User input is a critical part of almost every mobile application. From login forms to checkout pages, apps rely on user-provided data to perform essential tasks. However, one of the most important aspects of handling user input is ensuring that the data is valid before using it. Without validation, apps may process incomplete, incorrect, or even harmful data, leading to poor user experience and potential security issues.
Flutter provides an intuitive way to build forms and validate user input using the Form widget and the TextFormField widget. In this article, we will dive deep into the basics of form validation, focusing on validator functions, error messages, and required field validation.
By the end of this guide, you will be able to confidently build forms that guide users toward providing correct input, improving both usability and data reliability.
Why Form Validation Matters
Before we dive into code, let’s understand why validation is so important:
- Data Integrity: Ensures that information entered by users is complete and accurate.
- User Guidance: Helps users understand what is wrong with their input and how to fix it.
- Security: Prevents malicious input such as scripts or invalid data formats.
- Professional Experience: Users expect modern apps to guide them and prevent errors.
Without proper validation, a login form could accept empty usernames, or a checkout form could proceed without an address. This would not only break functionality but also frustrate users.
Form and TextFormField in Flutter
In Flutter, Form is a widget designed to group multiple input fields together. It works in combination with TextFormField, which is like a TextField but with built-in support for validation.
Example of a Basic Form
class SimpleForm extends StatefulWidget {
@override
_SimpleFormState createState() => _SimpleFormState();
}
class _SimpleFormState extends State<SimpleForm> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(20),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Enter your name"),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print("Form is valid");
}
},
child: Text("Submit"),
)
],
),
),
),
);
}
}
Key Components
- Form: Wraps around input fields and manages their validation state.
- GlobalKey<FormState>: A key that lets you access and control the form’s state (like calling
validate()). - TextFormField: An input field that can run validation logic via a
validatorfunction.
Validator Functions in TextFormField
The TextFormField widget has a property called validator, which accepts a function. This function defines the rules for validating input.
How Validator Works
- It runs whenever the form is validated using
FormState.validate(). - If the input is invalid, the function returns a string error message.
- If the input is valid, it returns null.
Example: Simple Validator
TextFormField(
decoration: InputDecoration(labelText: "Email"),
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter your email";
}
return null;
},
)
In this example:
- If the field is empty, the validator returns an error message.
- If the input is valid, it returns
null, meaning no error.
Multiple Validators
You can extend the validator function with multiple conditions:
TextFormField(
decoration: InputDecoration(labelText: "Password"),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter your password";
} else if (value.length < 6) {
return "Password must be at least 6 characters";
}
return null;
},
)
Here:
- The validator checks whether the password is empty.
- Then it checks if the password length is less than 6.
- It shows the appropriate message depending on the issue.
Showing Error Messages
One of the best features of TextFormField is that error messages are automatically displayed below the input field if the validator returns a string.
Example
TextFormField(
decoration: InputDecoration(labelText: "Username"),
validator: (value) {
if (value == null || value.isEmpty) {
return "Username cannot be empty";
}
return null;
},
)
When the form is validated and the field is empty:
- A red error message appears below the field.
- The border color also turns red, giving visual feedback.
Customizing Error Style
You can customize how error messages look using InputDecoration:
TextFormField(
decoration: InputDecoration(
labelText: "Name",
errorStyle: TextStyle(color: Colors.red, fontSize: 14),
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Name is required";
}
return null;
},
)
By adjusting errorStyle, you can change the color, size, or weight of the error text.
Simple Required Field Validation
The most common type of validation is required field validation, ensuring that a field cannot be left empty.
Example: Required Field
TextFormField(
decoration: InputDecoration(labelText: "First Name"),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "This field is required";
}
return null;
},
)
Why Use trim()?
- Users might accidentally enter spaces.
- Using
value.trim().isEmptyensures that inputs like" "are considered empty.
Multiple Required Fields
For forms with multiple fields, you can validate each field separately:
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "First Name"),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "First Name is required";
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Last Name"),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Last Name is required";
}
return null;
},
),
],
),
)
When the user taps Submit, Flutter validates both fields.
Validating on Button Press
To trigger validation, you call validate() on the form’s state:
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print("Form is valid");
} else {
print("Form has errors");
}
},
child: Text("Submit"),
)
This runs all validators in the form.
Validating on User Typing
Sometimes, you may want to validate input as the user types, not just on submit. For that, you can use:
autovalidateMode: AutovalidateMode.onUserInteraction
Example
TextFormField(
decoration: InputDecoration(labelText: "Email"),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (value == null || value.isEmpty) {
return "Email is required";
} else if (!value.contains("@")) {
return "Enter a valid email";
}
return null;
},
)
This shows validation feedback as soon as the user interacts with the field.
Best Practices for Form Validation
1. Keep Error Messages Clear and Helpful
Instead of saying “Invalid input,” say “Password must be at least 6 characters.”
2. Use Consistent Validation Across Forms
If multiple forms have email fields, apply the same validation logic everywhere.
3. Avoid Overloading Users with Errors
Do not show all errors at once while typing; prefer validation on submit or after interaction.
4. Combine Client-Side and Server-Side Validation
Even if the app validates input, the backend must also validate it for security.
5. Use Regular Expressions for Advanced Validation
For example, validating emails or phone numbers with regex.
Real-World Example: Login Form with Validation
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(20),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Email"),
validator: (value) {
if (value == null || value.isEmpty) {
return "Email is required";
} else if (!value.contains("@")) {
return "Enter a valid email";
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Password"),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return "Password is required";
} else if (value.length < 6) {
return "Password must be at least 6 characters";
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print("Login successful");
}
},
child: Text("Login"),
)
],
),
),
),
);
}
}
Features of This Login Form
- Uses validator functions in
TextFormField. - Shows error messages directly under fields.
- Requires both email and password.
- Validates password length.
Leave a Reply