DEV Community

Cover image for Create and Handle Flutter Input Form like a Pro
James Eneh
James Eneh

Posted on

Create and Handle Flutter Input Form like a Pro

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.
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: null,
)
Enter fullscreen mode Exit fullscreen mode

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.
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',
+        ),
+      ),
+    ],
+  ),
)
Enter fullscreen mode Exit fullscreen mode

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.
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',
         ),
      ),
    ],
  ),
)
Enter fullscreen mode Exit fullscreen mode

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

Next and Done Action Keys on Android
Step 4: Non-Empty Validation of Input Fields and Form Submission
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'),
+     ),
    ],
  ),
)
Enter fullscreen mode Exit fullscreen mode

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

The User Signup Form Screen

You can check out the complete code here.

Thanks for reading! 🙏🏼

Top comments (2)

Collapse
 
am_nkiruka profile image
Odu Nkiruka

Thank you for sharing this well written and comprehensive article, it's a breakthrough!!

Collapse
 
itamer profile image
Sarah

Has the syntax for validator changed?

I can get it to work if the function is inline but not if I call it the way you have coded it.