I'm trying to create a unique username for each user on my app. At sign up, I'm storing that username value in a separate collection ('Usernames') so it's easier to go through them when I check if the username is unique, instead of going through each user and their fields.
On my signup.dart I have a text field validator:
TextFormField(
style: TextStyle(fontSize: 16.0, color: Colors.white),
decoration: buildSignInInputDecoration("Username"),
validator: (value) {
if (value.length < 2 || value.isEmpty) {
return "Username is too short.";
} else if (value.length > 12) {
return "Username is too long.";
} else {
return null;
},
onSaved: (value) => _username = value.trim(),
),
}
And I made a function to check if username is already in the collection: (_username is the string that stores user input)
Future<bool> usernameCheck() async {
final snapShot = await usernamesRef.document(_username).get();
if (snapShot == null || !snapShot.exists) {
return true; //username is unique.
} else {
return false; //username exists.
}
}
My question is, how can I call usernameCheck and validate it with my validator? Thanks in advance!
UPDATE: I implemented the answer given bellow, but somehow valid always returns false, even if the username doesn't exist.
Future<bool> usernameCheck(String _username) async {
final result =
await usersRef.where("username", isEqualTo: _username).getDocuments();
return result.documents.isEmpty;
}
//Gets called on sign up button pressed.
Future<void> validateAndSubmit() async {
final valid = await usernameCheck(_username);
if (!valid) {
print("Username Exists.");
} else if (validateAndSave()) {
try {
// Signs up and save data.
You can't do asynchronous stuff inside a validator function.
What you can do, is call checkUsername()
before calling the validator, let's say you have a button, and when the user taps on it, you call the validator, you'll have something like this.
onPressed: () async {
final valid = await usernameCheck();
if (!valid) {
// username exists
}
else if (_formKey.currentState.validate()) {
// save the username or something
}
}
Also, you don't have to store the usernames in a separate collection, you can store them only in your main collection, let's say users
, and edit your checkUsername()
like this:
Future<bool> usernameCheck(String username) async {
final result = await Firestore.instance
.collection('users')
.where('username', isEqualTo: username)
.getDocuments();
return result.documents.isEmpty;
}
I was facing the same problem, and I want to check if the username is available or not automatically whenever the user changes his username. So, I have to use "onChanged" listener to get the username update then call usernameCheck(). but by this way, we will call usernameCheck() too many times in just a second.
for more details:
an instance of usernameCheck() will be called for every change in username TextField.
Also, the only instance that is going to be valid is the latest one, because the earlier will be called on a few letters only. that's why we have to eliminate the earlier instances.
so I have found a workaround to solve this.
TextFormField(
initialValue: user.userName,
textInputAction: TextInputAction.next,
style: TS_Style,
keyboardType: TextInputType.name,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_mailFocusNode);
},
onSaved: (value) => userProvider.setUserName = value,
validator: validateUsername,
onChanged: availableUsername,
decoration: TEXT_FIELD_DECORATION.copyWith(
prefixIcon: Icon( Icons.person, color: COLOR_BACKGROUND_DARK),
hintText: '@username',
suffixIcon: _checkingForUsername
? AspectRatio(
aspectRatio: 1,
child: Padding(
padding: const EdgeInsets.all(8),
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
)
: userNameAvailable != null
? (userNameAvailable ? Icon(Icons.check_sharp, color: Colors.green) : _emailCheckIcon = Icon(Icons.cancel, color: Colors.red))
: null),
)
_checkingForUsername is just a bool flag to show a loading indicator.
Future<void> availableUsername(String username) async {
//exit if the username is not valid
if (validateUsername(username) != null) return;
//save the username
_formKey.currentState.save();
setState(() => _checkingForUsername = true);
userNameAvailable = await handleRequest(() => userProvider.checkUserName(username), widget.scaffoldKey.currentContext);
setState(() => _checkingForUsername = false);
return;
}
handleRequest() is a function that takes the server function to handle its errors if something went wrong.
///check if the usename available or not
Future<bool> checkUserName(String calledValue) async {
//wait a second, is the user finished typing ?
//if the given value != [_username] this means another instance of this method is called and it will finish the mission
await Future.delayed(Duration(seconds: 1,milliseconds: 500));
if (calledValue != _userName) {
print('cancel username check ');
return null;
}
final result = await FirebaseFirestore.instance.collection('usernames').doc(_userName).get();
print('is Exist: ${result.exists}');
return !result.exists;
}
what exactly happens is when checkUserName(username) gets called, it waits 1.5 seconds (it could be only one second) then it compares the provider _username value (always up to date by availableUsername(username) method) and it's username parameter. if they are not equal it will terminate the process by returning null, otherwise, it will proceed and call the server.
by this pattern, it will call the server mostly for every desired call.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With