Flutter Forms Validation - the Ultimate Guide
Flutter Forms Validation - the Ultimate Guide
You’ve joined an innovative new company called Prestige Worldwide to create their app using Flutter. It’s a great app that lets your users register with their email address and password. You release the app to great fanfare, it goes viral and 10,000 people register on day one.
Fortune and fame await you! Sounds good so far? Not so fast, cowboy.
It turns out that half of your users gave you email addresses like “none of your business” and “you can’t have my email”. The other half have passwords so weak they’re bound to be cracked within a day or two, exposing Prestige Worldwide to fraud.

Alas! If only you’d put data validation into your forms, your reputation and bank balance would’ve been safe.
In this tutorial, you’ll learn:
- How to implement validations without a form.
- How to implement validations with a form.
- How to implement validations on non-textfields.
- How to autovalidate forms on user input.
- How to control form flow with FocusNodes.
Note: This tutorial assumes you’re already be familiar with Flutter development. If you’re new to Fluter, take a look at this awesome list, watch one of the Flutter team’s videos here, or pick up my Flutter book. Hope these help you get started.
Getting Started
You can get the starter project from github here. There’s a completed project and a starter so you can work along with the tutorial in the steps below.
Open the starter project in Visual Studio Code or Android Studio by navigating to the starter folder. Click Get dependencies or Get pub. Build and run the app, and you will see a form that’s ready to go, except for validations.

Enter some bad data like a one-character email address and password. Tap the save floating action button in the lower right corner and look in the Debug Console. This is the message it prints:
The user has registered with an email address of 'null' and a
password of 'null'
Your app approved the data and pretended to submit it to your server. That’s not good! The app should refuse to submit until the data is good. The best solution is to validate the data using a Flutter Form widget.
Why Use a Form?
In Flutter, you have two options for collecting user data:
| Without a Form | With a Form |
|---|---|
| Simpler but… | More complex but… |
| You have very little control over the fields | You have lots of control over the fields |
| Fields are unaware of each other | Field data can be evaluated as a group |
| Validations are manual so you have to write more code | Validations are easier because they’re declarative |
You can choose an option by asking yourself a quick question: Do I have a simple field or two or do I have more advanced requirements? If you have a simple field or two then do yourself a favor and use the first option (No Form). You can achieve this with a TextField widget. If you have more advanced requirements, you’ll need a Form widget.
With that out of the way, it’s time to use a Form widget.
Adding a Form
Open Register.dart and you’ll see the following:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Prestige Worldwide Registration")),
// TODO 1: Wrap the body in a Form widget
body: Container(
alignment: Alignment.center,
child: Column(
children: <Widget>[
_buildEmailField,
_buildPasswordField,
_buildPasswordConfirmationField,
_buildAgreeToTermsField,
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.save),
onPressed: _doRegister,
),
);
}
Find the line with TODO 1: and wrap the Container with Form like this:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Prestige Worldwide Registration")),
// TODO 1: Wrap the body in a Form widget
body: Form( // <-- Add this widget. (Don't forget to close it!)
child: Container(
alignment: Alignment.center,
child: Column(
children: <Widget>[
_buildEmailField,
_buildPasswordField,
_buildPasswordConfirmationField,
_buildAgreeToTermsField,
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.save),
onPressed: _doRegister,
),
);
}
If you rebuild and run the app at this point, you won’t see any difference. Like