I recently wrote an article on the Flutter Stepper widget which allows you to take a form (or similar content) and break it down into parts. If you're new to Flutter you're probably thinking... yeah, that's cool, but I have no idea how to work with forms to begin with.

Well, if this is you, we'll be remedying that by jumping into a Form example and subsequent validation.

Project setup

As always, let's create a blank project that we can use as a starting point:

$ flutter create flutter_forms && cd flutter_forms

$ code .

We can then go ahead and update main.dart to simply return an AccountPage as the home Widget at /pages/account_page.dart:

import 'package:flutter/material.dart';
import 'package:flutter_forms/pages/account_page.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: AccountPage());
  }
}

Our AccountPage will simply be a StatelessWidget that returns a StatefulWidget named AccountForm.

import 'package:flutter/material.dart';
import 'package:flutter_forms/forms/account_form.dart';

class AccountPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Account Validation'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: AccountForm(),
      ),
    );
  }
}

Let's go ahead and create the AccountForm at /forms/account_form.dart:

import 'package:flutter/material.dart';

class AccountForm extends StatefulWidget {
  @override
  _AccountFormState createState() => new _AccountFormState();
}

class _AccountFormState extends State<AccountForm> {

  @override
  Widget build(BuildContext context) {
    return Form()
  }
}

Creating a form

We'll start off by creating a uniquely identifiable key for our AccountForm of type GlobalKey<FormState>.

This allows us to reference our form in the future.

final _formKey = GlobalKey<FormState>();

Make sure you don't make the mistake of making this of type AccountFormState! :)

This also gives us the ability to call methods such as save, saving the value of each descendant FormFieldwithin our Form. We can also use reset, validate, and more - all things to look forward to!

We can now add the _formKey to our Form:

@override
Widget build(BuildContext context) {
  return Form(key: _formKey);
}

Adding a field

Next up, we'll look at adding a form field to our Form. In order to keep this as simple as possible, we'll stick with one field responsible for a person's fullName.

  @override
  Widget build(BuildContext context) {
    return Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            TextFormField(
              decoration: InputDecoration(
                  hintText: 'Name',
                  helperText: 'This has to be over two characters in length.'),
            ),
          ],
        ));
  }

Adding validation

Sweet. We've now got a field with the placeholder text of Name. We can go ahead and add some validationrules to this:

TextFormField(
  validator: (value) {
    if (value.isEmpty) {
      return "You can't have an empty name.";
    }

    if (value.length < 2) {
      return "Name must be more than one character.";
    }
  },
  decoration: InputDecoration(
      hintText: 'Name',
      helperText: 'This has to be over two characters in length.'),
),

For our example application we're making the arbitrary decision that a name can't be lower than two characters. Change this to match the validation rules of your particular form field.

Determining if a Form is valid

Next, let's see if a Form is valid by checking the validate method when a RaisedButton is clicked:

  @override
  Widget build(BuildContext context) {
    return Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            TextFormField(
              validator: (value) {
                if (value.isEmpty) {
                  return "You can't have an empty name.";
                }

                if (value.length < 2) {
                  return "Name must be more than one character.";
                }
              },
              decoration: InputDecoration(
                  hintText: 'Name',
                  helperText: 'This has to be over two characters in length.'),
            ),
            RaisedButton(
              onPressed: () {
                _formKey.currentState.validate()
                    ? Scaffold.of(context)
                        .showSnackBar(SnackBar(content: Text('This is valid!')))
                    : Scaffold.of(context)
                        .showSnackBar(SnackBar(content: Text('Not valid!')));
              },
              child: Text('Submit'),
            )
          ],
        ));

This gives us the following results:

Form Validity

Accessing form values

Earlier in the article I mentioned the use of a save method. Let's look at how we can add an onSaved event to our TextFormField and update a local variable such as _name:

Firstly, we declare a variable such as name at the class level:

class _AccountFormState extends State<AccountForm> {
  String _name;
}

Next, on our TextFormField itself we can add the onSaved which returns the entered value as a String:

TextFormField(
  onSaved: (String val) => setState(() => _name = val),
);

We're using this to update the value of _name and can show it on our screen like this:

Text('Name: $_name')

We can now run save whenever our form is valid, checking the validity first with validate:

RaisedButton(
  onPressed: () {
    if (_formKey.currentState.validate()) {
      _formKey.currentState.save();
      Scaffold.of(context)
          .showSnackBar(SnackBar(content: Text('This is valid!')));
    } else {
      Scaffold.of(context)
          .showSnackBar(SnackBar(content: Text('Not valid!')));
    }
  },
  child: Text('Submit'),
),

Here's what this gives us:

Form validation: Save

Auto validation

What if we wanted to validate the form without having to press Submit? Turns out we can do that with a nifty flag named autovalidate:

return Form(
  key: _formKey,
  autovalidate: true,
)

Now any time we start typing, the form is automatically validated!

Summary

We've learned the basics of Form validation with Flutter. Using this new knowledge, go forth and validate!