Flutter UI Widgets

Nov 9 2021 Dart 2.14, Flutter 2.5, VS Code 1.61

Part 1: Flutter UI Widgets

10. Work with Forms

Episode complete

Play next episode

Next
About this episode
See versions

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 9. Add Navigation & SliverAppBar Next episode: 11. Display Dialogs

This video Work with Forms was last updated on Nov 9 2021

Forms are very essential in almost every app. Flutter gives us various ways to handle user inputs. We can manually handle inputs using various techniques but in this episode, we’ll be using the Form widget which is more robust. I’ve linked the FAB to navigate to the CreateArticle page.

This is included in the starter project of this episode.

Before we start coding the form, we need a way to hold the data that is being entered in the form. For this, i’ve provided the FormData model which is stored inside the models folder. Go ahead and open it up. It has all the values we intend collecting from the form. Normally, we should have used the Article mode. But it contains only strings and that’s kind of limiting for this example since we would be covering other input widgets. And that’s why the custom FormData model was created.

Let’s go ahead and make use of it. Head back to the create article page and enter the following code:

...
FormData formData = FormData();
...

This creates a formData object that would hold our form field values. Next, let’s create a basic form. Paste in the following code:

body: Padding(
  padding: const EdgeInsets.all(16),
  child: Form(
    child: ListView(
      children: <Widget>[
        TextFormField(
          decoration: const InputDecoration(labelText: 'Title'),
          validator: (value) =>
              value!.isEmpty ? 'Please enter Title' : null,
          onSaved: (value) => formData.title = value,
        ),
        ElevatedButton(
          onPressed: () {},
          child: const Text('Save'),
        )
      ],
    ),
  ),
),

Save your work. And you can see the corresponding UI. We returned a Form which contains a TextFormField and a ElevatedButton. A Form is simply a Container for form fields just like the one we added. You’ll see the benefit of using a Form shorthly.

A TextFormField is a FormField that contains a TextField. With this widget, you get a TextEditingController automatically. If you used a TextField, you would have to manually connect a TextEditingController to retrieve its value.

We set its label text in the InputDecoration class. This class is used to style inputs with borders, icons, hint text and other styling attributes. Also, we added a validator. The validator callback function checks to see if the text is empty. If it is empty, the validation fails and returns an error message. If there are no errors, the validator must return null.

Next, the onSaved method is called when we submit the form. Here, set the of the formData’s title to the input value. Now, you might ask: How do we submit the form? Well, that leads us to the next stage.

The form needs a key. Keys are used in Flutter to uniquely identify widgets and they contain some information. We would be using a GlobalKey to store the state of the Form. GlobalKeys are unique across the entire app. Okay, let’s add it now:

...
final _formKey = GlobalKey<FormState>(); // As class member
...
child: Form(
  key: _formKey,
...

In here, we created a GlobalKey of type FormState. A FormState object can be used to save, reset and validate every FormField that is inside the corresponding form.

Next, we assign the _formKey to the key property of the Form widget. Let add the code to save the Form when we hit the save button. Add the following code to the onPressed method of the button:

...
onPressed: () {
  final form = _formKey.currentState;
  if (form.validate()) {
    form.save();
    print(formData);
    form.reset();
  }
  FocusScope.of(context).unfocus();
},
...

Save your work after the update. We get the currentState of the Form from the formKey variable. Then we check to see if the form is valid. The validate method, executes the validator of each FormField. It returns true if every FormField is valid. The form would then rebuild to report its result. This is possible, because the Form is a StatefulWidget.

If the form is valid, then we call the save method and this calls the onSaved method for all the FormFields that is associated with the Form. And the onSaved method saves the value of the textfield to the formData object. And we can see that from the TextFormField we created earlier.

Next, we print the formData to the debugConsole and finally, we clear the form by calling the reset method. You see, all these bulk operations are possible because of we’re using the Form widget which has the necessary functionalities to process a inputs effectively. And the last line programmatically hides the keyboard when you press the submit button. Okay, enough talk, let’s try it out.

First, let’t submit the form with an empty title field. You can see the validator kicks in and declares the form invalid. And shows the corresponding error message. Now, let’s add some text. And we submit the form. You can see the TextField is cleared and let’s check the console to see if it prints out the value. Open up the debug console by pressing Ctrl + Shift + Y on windows or Shift + Cmd + Y for mac users.

And you can see it prints out an instance of the FormData class. Now we got the form setup with the title field, let’s implement other fields. I’ll update this file and explain the new additions.

I’ve added other form fields here. Some of them are very similar to the title text. I also added some custom validation, although in a production app, you’ll write better validations or use a library. Let’s go over the easy ones first. This is the image url text form field.

And its validator simply checks to see if the text entered is an actual url.

The content validator check if the field is empty or has a length less than 10. The email validator calls a function that validates the email.

Let’s scoll up to that function.In this method, i declared a string pattern that conforms to the format of an email address. Next, we pass this string to a RegularExpression. We check to to see if the value entered matches the regular expression pattern. If it doesn’t match, we return an error message. If it does, we return null to signify the value was valid. And by the way, i spent 2 years memorizing this string pattern. It’s very difficult but doable.

Okay seriously, you can find regular expression patterns all over the internet. I copied this one from the internet. Next, we have the SwitchListTile widget. It is a Switch widget with some additional styling to make it look like a ListTile. This widget does not manage its state like the TextFormField so we manually call setState to update the formData with its value when the user toggles the switch. And we do this in the onChanged callback function.

Next, is the RadioListTile widget. These are radio buttons where the user selects only one value from the group. Just like the SwitchListTile, it does not manage its state so we use setState when the radio button is selected. And one property to take note of is the groupValue. You assign both buttons the same groupValue variable. This links the two radio buttons together so that you can only select one of them.

Now that we’re done with all the explanations, we need to fill the form and try it out. But before that, we need to reset the state of both the Switch and the Radio button to their initial values since they are not linked to the form’s state.

Enter the following code in the onPressed method:

...
setState(() {
  formData.isBreaking = false;
  formData.category = null;
});

Save your work. And let’s fill up the form. Then you click on the save button. And you have the form values saved successfully and the form reset. Open up your debugConsole to see the values. And you can see the formData object has all the form values.