Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unique username in Firebase with Flutter

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.
like image 426
itslucca Avatar asked Oct 17 '25 15:10

itslucca


2 Answers

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;
  }
like image 83
Abdou Ouahib Avatar answered Oct 21 '25 00:10

Abdou Ouahib


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.

1. TextFormField


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.

2. availableUsername(username) method


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.

3. checkUserName(username)


  ///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.

like image 32
Mostafa Osama Avatar answered Oct 21 '25 01:10

Mostafa Osama