How to validate forms and user input the easy way using Flutter
Coming from an Android background, validating forms and user input requires lines upon lines of boilerplate code.
First, you have to get a reference to the EditText (or any View for that matter). Except if you are using Kotlin Android Extensions, you have to resort to the good old findViewById()
or @BindView
— for those using Jake Wharton’s ButterKnife. After which, you get a reference to the text and do any validation on it.
Now imagine doing that for a UI that requires 10 different types of user input. You practically have to write a jungle of nested if-else statements.
How is User Input Validation Handled in Flutter?
Now, Flutter comes with a very simple form validation API. It just requires the use of Form and TextFormField widgets.
Form is a container for FormFields (and its descendants) and other widgets. We also have save
, validate
, and reset
functions. These facilitate easier validation and manipulation of data inputted in one or multiple FormFields.
TextFormField is just a FormField that contains a TextField. It has onSaved
and validator
optional typedef parameters. Both functions are called when save
and validate
are respectively called on the container Form.
How to Validate Forms and User Inputs in Flutter.
So, let’s dive in into the actual code…
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String email;
String password;
bool _autoValidate = false;...new Form(
key: _formKey,
autovalidate: _autoValidate,
child: new Column(
children: <Widget>[
new SizedBox(
height: 20.0,
),
new TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
onSaved: (String value) {
email = value;
},
validator: _validateEmail,
keyboardType: TextInputType.emailAddress,
),
new SizedBox(
height: 20.0,
),
new TextFormField(
decoration: const InputDecoration(labelText: 'Password'),
onSaved: (String value) {
password = value;
},
validator: _validatePassword,
obscureText: true,
),
new SizedBox(
height: 20.0,
),
new RaisedButton(
onPressed: _validateInputs,
child: new Text('Login'),
)
],
))
I’ll explain the code in snippets below:
new Form(
key: _formKey,
autovalidate: _autoValidate,
Here, we are creating a container for FormFields. While the key
property is for assessing the current state of the Widget, the autovalidate
property is to toggle auto validation of all the FormFields in this Form container. When autovalidate
is set to true, the validate
function of this Form is called each time a character is entered in any of the FormFields.
new TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: _validateEmail,
onSaved: (String value) {
email = value;
},
keyboardType: TextInputType.emailAddress,
)
In the above code snippet, we’re simply instantiating a TextFormField. The actual validation happens in the validator
property. We either return null
or an error string. Returning null
means that the value entered by the user is OK and conforms with the format that is expected. If we return an error string, that means that something is wrong with the user input. This should be user-readable as it is what is displayed to the user.
This is how the email validation code looks:
String _validateEmail(String value) {
if (value.isEmpty) {
// The form is empty
return "Enter email address";
}
// This is just a regular expression for email addresses
String p = "[a-zA-Z0-9\+\.\_\%\-\+]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+";
RegExp regExp = new RegExp(p);
if (regExp.hasMatch(value)) {
// So, the email is valid
return null;
}
// The pattern of the email didn't match the regex above.
return 'Email is not valid';
}
After validating the Form, we call save
on it, and this will in turn call onSaved
of all the containing TextFormFields. In onSaved
, we simply assign the value to the email
variable.
new RaisedButton(
onPressed: _validateInputs,
child: new Text('Login'),
)
This is just a button that calls _validateInputs
function when pressed.
void _validateInputs() {
final form = _formKey.currentState;
if (form.validate()) {
// Text forms was validated.
form.save();
} else {
setState(() => _autoValidate = true);
}
}
First, we got the current state of the Form with its key, and we called validate
on it. Just as I have said before, calling validate
calls the validator
function of all the FormFields in this Form. Now, validate
returns true if the validator
function of all the FormFields returns null
, otherwise, it returns false.
We also called save on the Form after validating all user inputs. Again, this calls onSaved
of individual FormFields.
You noticed that we set _autoValidate
to true, right? This is because we want the Form to start auto-validating after the validation fails the first time.
So that’s the way it works.
What about Check Boxes and Radio Buttons?
Well, there might be a cleaner way to do this. But the only way I could think of was an “Android-like” validation on the check boxes and radio buttons.
First, I created the Widget:
...
bool _termsChecked = false;
int radioValue = -1;
...new Column(
children: <Widget>[
new RadioListTile<int>(
title: new Text('Male'),
value: 0,
groupValue: radioValue,
onChanged: handleRadioValueChange),
new RadioListTile<int>(
title: new Text('Female'),
value: 1,
groupValue: radioValue,
onChanged: handleRadioValueChange),
new RadioListTile<int>(
title: new Text('Transgender'),
value: 2,
groupValue: radioValue,
onChanged: handleRadioValueChange),
],
),
new SizedBox(
height: 20.0,
),
new CheckboxListTile(
title: new Text('Terms and Conditionns'),
value: _termsChecked,
onChanged: (bool value) =>
setState(() => _termsChecked = value)),
Then I made changes to my _validateInputs
function.
void _validateInputs() {
final form = _formKey.currentState;
if (form.validate()) {
// Text forms has validated.
// Let's validate radios and checkbox
if (radioValue < 0) {
// None of the radio buttons was selected
_showSnackBar('Please select your gender');
} else if (!_termsChecked) {
// The checkbox wasn't checked
_showSnackBar("Please accept our terms");
} else {
// Every of the data in the form are valid at this point
form.save();
}
} else {
setState(() => _autoValidate = true);
}
}
So that’s it. If you have a cleaner way to validate check boxes and radio buttons, please let me know.
The full working code can be found in the repo here.