Create and Handle Flutter Input Form like a Pro
Tags: flutter, dart, android, ios
Published: 2021-02-26 01:50:22

Input forms are used in software applications to collect data from users, they're an indispensable component that makes up the user interfaces of any application. We see them play crucial roles in scenarios like; user signup & login, sending a message on an instant messaging app, checking out orders on an e-commerce app, and so on.  That being said, it's important to make input forms readily accessible and easy to navigate by users.

The aim of this article is to demonstrate how to create and, handle a  Flutter Form with TextFormField widgets for a better user experience. What will be covered here include;
* Explain what Flutter Form and TextFormField classes are.
* Discuss and show how some properties of a TextFormField widget can be used to achieve ease of navigation from one TextFormField to another.
* Discuss and show how some Form and TextFormField widgets’ properties can be used for a basic form field validation.

*Note: This article is intended for readers with a basic knowledge of Flutter (i.e., readers who can create and run a Flutter application on an Android or iOS device, and also understand what widgets (both stateful and stateless) are, as used in Flutter).*

A Flutter Form class is a widget that can be used to wrap/group form fields for easy save, reset, or validation of the grouped form fields. A TextFormField class, on the other hand, is a widget that wraps a TextField widget in a FormField widget.

Let's dive into how Form and TextFormField widgets, and some of their properties can be used to accomplish ease of navigation from one input field to another, and form validation. A user signup form with four input fields for capturing the user's name, phone, email, and password will be used as a case study.
*At this point, you must have created a Flutter project with any valid name of your choice.*

##### Step 1: Let's Create an Instance of a Form Widget with a Global Key that holds the Form's State.

```dart
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: null,
)
```

The private variable identifiable with the name `_formKey` was set to a global key holding the form state, and it's passed to the Form widget as a key. This key will allow the form to validate all its descendant input fields.

##### Step 2: Add Four TextFormField Widgets with some Basic Properties to the above Form.

```diff
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
-  child: null,
+  child: Column(
+    children: <Widget>[
+      TextFormField(
+        keyboardType: TextInputType.name,
+        textInputAction: TextInputAction.next,
+        decoration: InputDecoration(
+          hintText: 'Enter your full name',
+          labelText: 'Full Name',
+        ),
+      ),
+      TextFormField(
+        keyboardType: TextInputType.phone,
+        textInputAction: TextInputAction.next,
+        decoration: InputDecoration(
+          hintText: 'Enter your phone number',
+          labelText: 'Phone Number',
+        ),
+      ),
+      TextFormField(
+        keyboardType: TextInputType.emailAddress,
+        textInputAction: TextInputAction.next,
+        decoration: InputDecoration(
+          hintText: 'Enter your email address',
+          labelText: 'Email Address',
+        ),
+      ),
+      TextFormField(
+        keyboardType: TextInputType.text,
+        textInputAction: TextInputAction.done,
+        obscureText: true,
+        decoration: InputDecoration(
+          hintText: 'Enter your password',
+          labelText: 'Password',
+        ),
+      ),
+    ],
+  ),
)
```
The four TextFormFields widgets are for capturing the user's name, phone number, email address, and password. Let's discuss the properties passed to the TextFormField widgets;

`keyboardType`: the type of soft keyboard to pop up when the text field gains focus, passing an appropriate text input type value to this property will add to a better user experience. For example, passing `TextInputType.emailAddress` to the `keyboardType` property of the email address TextFormField widget will display the appropriate keyboard for typing an email address with `@` and `.` characters present when the text field gains focus.

`textInputAction`: its value determines the icon to appear as the keyboard’s action key. When set to `TextInputAction.next`; the `next` icon appears as the action key. We will see how intuitive and descriptive this is for navigating from one text field to another in the next step.

`obscureText`: it takes a boolean value (with `false` as default) that determines whether or not the data supplied by the user in the text field is to be obscured. The data is obscured when it's set to `true`, which applies to the password TextFormField widget.

`decoration`: It takes an instance of the InputDecoration class whose properties describe how the text field is decorated, as its value. Here, only two of its properties are in use;
1. `hintText`: a placeholder text, shown when the text field is empty, and
2. `labelText`: a descriptive label text, shown when the text field has focus or not empty.

##### Step 3: Make the Form Navigable and Submittable via the Keyboard’s Action Key.

```diff
final _formKey = GlobalKey<FormState>();
+
+ final FocusNode _nameFocusNode = FocusNode();
+ final FocusNode _phoneFocusNode = FocusNode();
+ final FocusNode _emailFocusNode = FocusNode();
+ final FocusNode _passwordFocusNode = FocusNode();
+
+ _nextFocus(FocusNode focusNode) {
+   FocusScope.of(context).requestFocus(focusNode);
+ }
+
+ _submitForm() {
+   Scaffold.of(context).showSnackBar(SnackBar(content: 
+   Text('Registration sent')));
+ }

Form(
  key: _formKey,
  child: Column(
    children: <Widget>[
      TextFormField(
         keyboardType: TextInputType.name,
         textInputAction: TextInputAction.next,
+        focusNode: _nameFocusNode,
+        onFieldSubmitted: (String value) {
+          //Do anything with value
+          _nextFocus(_phoneFocusNode);
+        },
         decoration: InputDecoration(
           hintText: 'Enter your full name',
           labelText: 'Full Name',
         ),
      ),
      TextFormField(
         keyboardType: TextInputType.phone,
         textInputAction: TextInputAction.next,
+        focusNode: _phoneFocusNode,
+        onFieldSubmitted: (String value) {
+          //Do anything with value
+          _nextFocus(_emailFocusNode);
+        },
         decoration: InputDecoration(
           hintText: 'Enter your phone number',
           labelText: 'Phone Number',
         ),
      ),
      TextFormField(
         keyboardType: TextInputType.emailAddress,
         textInputAction: TextInputAction.next,
+        focusNode: _emailFocusNode,
+        onFieldSubmitted: (String value) {
+          //Do anything with value
+          _nextFocus(_passwordFocusNode);
+        },
         decoration: InputDecoration(
           hintText: 'Enter your email address',
           labelText: 'Email Address',
         ),
      ),
      TextFormField(
         keyboardType: TextInputType.text,
         textInputAction: TextInputAction.done,
         obscureText: true,
+        focusNode: _passwordFocusNode,
+        onFieldSubmitted: (String value) {
+          //Do anything with value
+          _submitForm();
+        },
         decoration: InputDecoration(
           hintText: 'Enter your password',
           labelText: 'Password',
         ),
      ),
    ],
  ),
)
```
The `focusNode` property of every TextFormField widget in the `Form` is assigned a FocusNode object to make the form easily navigable from one input field to another. Here, a FocusNode object allows keyboard focus to be obtained and pass around amongst input fields. We have four FocusNode objects (`_nameFocusNode`, `_phoneFocusNode`, `_emailFocusNode`, and `_passwordFocusNode`) declared, initialized and passed to the four TextFormField widgets in our form respectively.

The above code update added the `onFieldSubmitted` function property to the TextFormField widgets. This function takes a string argument which is the current value of its associated input field, and it's called when a user clicks the keyboard action key.

For `name`, `phone`, and `email` input fields, the `onFieldSubmitted` function is used to pass keyboard focus to the next input field when a user clicks the action key. Thus, their `textInputAction` properties are set to `TextInputAction.next` to make the action key's behaviour intuitive. The `onFieldSubmitted` function calls the `_nextFocus()` method with the next `FocusNode` object as a parameter to pass the keyboard focus to the next input field. The `_nextFocus()` does the passing of keyboard focus to the received FocusNode object.

The `onFieldSubmitted` property of the password `TextFormField` widget submits the form, and as such, the widget's `textInputAction` property has the value `TextInputAction.done` to depict the action key's behaviour. The `onFieldSubmitted` function calls the `_submitForm()` method, which flashes the message, `Registration sent` on screen. The `_submitForm()` method will do much more than flashing a message in the next step.

![Next and Done Action Keys](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4gec30snpmtdktz3ti4.png)
###### `Next` and `Done` Action Keys on Android


##### Step 4: Non-Empty Validation of Input Fields and Form Submission

```diff
final _formKey = GlobalKey<FormState>();

final FocusNode _nameFocusNode = FocusNode();
final FocusNode _phoneFocusNode = FocusNode();
final FocusNode _emailFocusNode = FocusNode();
final FocusNode _passwordFocusNode = FocusNode();
+
+ final TextEditingController _nameController = TextEditingController();
+ final TextEditingController _phoneController = TextEditingController();
+ final TextEditingController _emailController = TextEditingController();
+ final TextEditingController _passwordController = TextEditingController();

_nextFocus(FocusNode focusNode) {
  FocusScope.of(context).requestFocus(focusNode);
}

_submitForm() {
+  if (_formKey.currentState.validate()) {
+    final user = {
+      'name': _nameController.text,
+      'phone': _phoneController.text,
+      'email': _emailController.text,
+      'password': _passwordController.text,
+    };
+    print(user.toString());

     // If the form passes validation, display a Snackbar.
     Scaffold.of(context).showSnackBar(SnackBar(
       content:Text('Registration sent')));
+
+    _formKey.currentState.save();
+    _formKey.currentState.reset();
+    _nextFocus(_nameFocusNode);
+  }
}
+
+ String _validateInput(String value) {
+   if(value.trim().isEmpty) {
+     return 'Field required';
+   }
+   return null;
+ }

Form(
  key: _formKey,
  child: Column(
    children: <Widget>[
      TextFormField(
        keyboardType: TextInputType.name,
        textInputAction: TextInputAction.next,
        focusNode: _nameFocusNode,
        onFieldSubmitted: (String value) {
          //Do anything with value
          _nextFocus(_phoneFocusNode);
        },
+       controller: _nameController,
+       validator: _validateInput,
        decoration: InputDecoration(
          hintText: 'Enter your full name',
          labelText: 'Full Name',
        ),
      ),
      TextFormField(
        keyboardType: TextInputType.phone,
        textInputAction: TextInputAction.next,
        focusNode: _phoneFocusNode,
        onFieldSubmitted: (String value) {
          //Do anything with value
          _nextFocus(_emailFocusNode);
        },
+       controller: _phoneController,
+       validator: _validateInput,
        decoration: InputDecoration(
          hintText: 'Enter your phone number',
          labelText: 'Phone Number',
        ),
      ),
      TextFormField(
        keyboardType: TextInputType.emailAddress,
        textInputAction: TextInputAction.next,
        focusNode: _emailFocusNode,
        onFieldSubmitted: (String value) {
          //Do anything with value
          _nextFocus(_passwordFocusNode);
        },
+       controller: _emailController,
+       validator: _validateInput,
        decoration: InputDecoration(
          hintText: 'Enter your email address',
          labelText: 'Email Address',
        ),
      ),
      TextFormField(
        keyboardType: TextInputType.text,
        textInputAction: TextInputAction.done,
        obscureText: true,
        focusNode: _passwordFocusNode,
        onFieldSubmitted: (String value) {
          //Do anything with value
          _submitForm();
        },
+       controller: _passwordController,
+       validator: _validateInput,
        decoration: InputDecoration(
          hintText: 'Enter your password',
          labelText: 'Password',
        ),
      ),
+     ElevatedButton(
+       onPressed: _submitForm,
+       child: Text('Register'),
+     ),
    ],
  ),
)
```
The above code update added two new properties to the TextFormField widgets; 
1. the `controller` property: a TextEditingController object, and 
2. the `validator` property: a function that takes a string argument (value of the input field) and returns either a string value (validation error message) or `null`.

The `TextEditingController` object allows the value of an input field to be accessed even as a user types into the input field via its `text` property. Four `TextEditingController` objects have been instantiated and used as controllers in `TextFormField` widgets for the `name`, `phone`, `email`, and `password` fields.

A function named `_validateInput` has been defined; it takes a string argument `value` and either returns a string value '`Field required`' if the argument is empty or returns `null` if the argument is not empty. The `_validateInput()` is used as validators in all the form's input fields.

The `_submitForm()` function has been modified to print values of `name`, `phone`, `email`, and `password` input fields to console. Flash the message '`Registration sent`' on-screen. Save the form's current state, reset the form's current state and pass keyboard focus to the `name` `TextFormField` widget on successful form validation.

The `_formKey.currentState.validate()` calls the validators of the form's descendant `TextFormField` widgets; if all the validators return `null`, then it returns `true`, else it returns `false`.
The `_formKey.currentState.save()` saves all the form's descentant `TextFormField` widgets, while the `_formKey.currentState.reset()` resets all TextFormField widgets wrapped in the form to their initial values.

An `ElevatedButton` widget has also been added to the form to handle submission when pressed/clicked. It takes the function `_submitForm()` as its `onPressed` property and has a text that reads '`Register`' as its child widget.

![User Signup Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytx07p0maasjnky89j4j.png)
###### The User Signup Form Screen


You can check out the complete code [here.](https://github.com/erozonachi/flutter_form_handling)

*Thanks for reading!* 🙏🏼