Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flutter validate form asynchronously

new TextFormField(
                  validator: (value) async{
                    if (value.isEmpty) {
                      return 'Username is required.';
                    }
                    if (await checkUser()) {
                      return 'Username is already taken.';
                    }
                  },
                  controller: userNameController,
                  decoration: InputDecoration(hintText: 'Username'),
                ),

I have a form for user, and I want to check if the user already exists in the firestore datebase.

Future checkUser() async {
var user = await Firestore.instance
    .collection('users')
    .document(userNameController.text)
    .get();
return user.exists;

}

This is my function to check if the user document already exists in the database. But validator gives me this error.

[dart] The argument type '(String) → Future' can't be assigned to the parameter type '(String) → String'.

How should I fix this issue?

like image 887
Chris Jeong Avatar asked Jun 24 '18 17:06

Chris Jeong


2 Answers

At this time I think that you can't associate a Future to a validator.

What you can do is this verifying the data on a button click or in another way and set the state on the validator response var.

 @override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
    body: Form(
        key: _formKey,
        child: Column(children: [
          new TextFormField(
              validator: (value) {
                return usernameValidator;
              },
              decoration: InputDecoration(hintText: 'Username')),
          RaisedButton(
            onPressed: () async {
              var response = await checkUser();

              setState(() {
                this.usernameValidator = response;
              });

              if (_formKey.currentState.validate()) {}
            },
            child: Text('Submit'),
          )
        ])));
}
like image 70
Zroq Avatar answered Oct 19 '22 19:10

Zroq


I needed to do this for username validation recently (to check if a username already exists in firebase) and this is how I achieved async validation on a TextFormField ( without installation of any additional packages). I have a "users" collection where the document name is the unique username ( Firebase can't have duplicate document names in a collection but watch out for case sensitivity)

//In my state class
class _MyFormState extends State<MyForm> {
  final _usernameFormFieldKey = GlobalKey<FormFieldState>();

  //Create a focus node
  FocusNode _usernameFocusNode;

  //Create a controller
  final TextEditingController _usernameController = new TextEditingController();

  bool _isUsernameTaken = false;
  String _usernameErrorString;

 @override
  void initState() {
    super.initState();

    _usernameFocusNode = FocusNode();

    //set up focus node listeners
    _usernameFocusNode.addListener(_onUsernameFocusChange);

  }

  @override
  void dispose() {
    _usernameFocusNode.dispose();

    _usernameController.dispose();

    super.dispose();
  }
}

Then in my TextFormField widget

  TextFormField(
      keyboardType: TextInputType.text,
      focusNode: _usernameFocusNode,
      textInputAction: TextInputAction.next,
      controller: _usernameController,
      key: _usernameFormFieldKey,
      onEditingComplete: _usernameEditingComplete,
      validator: (value) => _isUsernameTaken ? "Username already taken" : _usernameErrorString,)

Listen for focus changes on the widget i.e when it loses focus. You can also do something similar for "onEditingComplete" method

void _onUsernameFocusChange() {
    if (!_usernameFocusNode.hasFocus) {

      
      String message = UsernameValidator.validate(_usernameController.text.trim());

      //First make sure username is in valid format, if it is then check firebase
      if (message == null) {
        Firestore.instance.collection("my_users").document(_usernameController.text.trim()).get().then((doc) {

          if (doc.exists) {
            setState(() {
              _isUsernameTaken = true;
              _usernameErrorString = null;
            });
          } else {
            setState(() {
              _isUsernameTaken = false;
              _usernameErrorString = null;
            });
          }
          _usernameFormFieldKey.currentState.validate();
        }).catchError((onError) {
          setState(() {
            _isUsernameTaken = false;
            _usernameErrorString = "Having trouble verifying username. Please try again";
          });
          _usernameFormFieldKey.currentState.validate();
        });
      } else {
        setState(() {
          _usernameErrorString = message;
        });
        _usernameFormFieldKey.currentState.validate();
      }
    }
  }

For completeness, this is my username validator class

class UsernameValidator {
  static String validate(String value) {
    final regexUsername = RegExp(r"^[a-zA-Z0-9_]{3,20}$");

    String trimmedValue = value.trim();

    if (trimmedValue.isEmpty) {
      return "Username can't be empty";
    }
    if (trimmedValue.length < 3) {
      return "Username min is 3 characters";
    }

    if (!regexUsername.hasMatch(trimmedValue)) {
      return "Usernames should be a maximum of 20 characters with letters, numbers or underscores only. Thanks!";
    }

    return null;
  }
}
like image 23
GraSim Avatar answered Oct 19 '22 18:10

GraSim