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,
)
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',
+ ),
+ ),
+ ],
+ ),
)
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;
-
hintText
: a placeholder text, shown when the text field is empty, and -
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',
),
),
],
),
)
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 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'),
+ ),
],
),
)
The above code update added two new properties to the TextFormField widgets;
- the
controller
property: a TextEditingController object, and - the
validator
property: a function that takes a string argument (value of the input field) and returns either a string value (validation error message) ornull
.
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.
The User Signup Form Screen
You can check out the complete code here.
Thanks for reading! 🙏🏼
Top comments (2)
Thank you for sharing this well written and comprehensive article, it's a breakthrough!!
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.