Form handling is one of the most common tasks in any application, whether you are building a login screen, a registration form, a search interface, or a checkout page. Flutter provides a powerful Form widget along with FormState and GlobalKey to make form management easier and more structured.
In this article, we will take a deep dive into:
- What is
GlobalKeyand why do we use it in forms. - How
FormStateworks behind the scenes. - Accessing form state using
GlobalKey<FormState>. - Using
formKey.currentState!.validate()for validation. - Resetting form fields programmatically.
- Best practices for managing forms.
- Real-world examples of login and signup forms.
By the end of this guide, you will understand how to confidently build forms in Flutter and handle form validation and reset operations like a pro.
Understanding Form Handling in Flutter
Flutter has a dedicated Form widget designed to manage multiple form fields together. While a TextField works fine for simple input, TextFormField works best inside a Form because it integrates with validation and form state management.
However, the real power comes from FormState, which lets you:
- Validate all fields at once.
- Save field values programmatically.
- Reset the form when needed.
But to access FormState, you need a GlobalKey.
What is GlobalKey in Flutter?
A GlobalKey is a unique key that provides access to a widget’s state from outside its widget tree.
When you create a GlobalKey<FormState>, it allows you to interact with the form and perform actions like validation and reset, even if you are outside the immediate context of the form widget.
Example
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
This key uniquely identifies the form and gives access to its state.
What is FormState?
The FormState class is automatically created when you use a Form widget. It stores the current state of the form and provides methods like:
validate()– checks if all fields are valid.save()– saves the values of form fields.reset()– resets all fields to their initial values.
When combined with GlobalKey<FormState>, these methods become extremely powerful.
Accessing Form State
Accessing the form state is one of the first steps when working with forms in Flutter. You do this by attaching a GlobalKey to your Form.
Example: Setting Up a Form with GlobalKey
class MyFormPage extends StatefulWidget {
@override
_MyFormPageState createState() => _MyFormPageState();
}
class _MyFormPageState extends State<MyFormPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Email"),
),
TextFormField(
decoration: InputDecoration(labelText: "Password"),
),
ElevatedButton(
onPressed: () {
// Accessing the form state
if (_formKey.currentState != null) {
print("Form state accessed successfully");
}
},
child: Text("Check Form State"),
),
],
),
),
),
);
}
}
Here, we are able to access the form state using _formKey.currentState.
Using formKey.currentState!.validate()
Validation is one of the most important tasks in form handling. Flutter makes this easy with FormState.validate().
When you call formKey.currentState!.validate(), it runs the validator functions of every TextFormField in the form.
Example: Simple Validation
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Email"),
validator: (value) {
if (value == null || value.isEmpty) {
return "Please enter an email";
}
if (!value.contains("@")) {
return "Invalid email format";
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Password"),
validator: (value) {
if (value == null || value.length < 6) {
return "Password must be at least 6 characters";
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print("Form is valid");
} else {
print("Form has errors");
}
},
child: Text("Login"),
),
],
),
),
),
);
}
}
How It Works
- Each
TextFormFieldhas avalidatorfunction. - When the login button is pressed,
_formKey.currentState!.validate()is called. - Flutter executes all validators and displays error messages if any validation fails.
Why Use validate()?
- It centralizes validation logic.
- It allows validating multiple fields together.
- It prevents proceeding with invalid inputs.
Resetting Form Fields Programmatically
Sometimes, you need to reset all form fields to their initial states. Flutter provides FormState.reset() for this purpose.
Example: Resetting Fields
class ResetFormPage extends StatefulWidget {
@override
_ResetFormPageState createState() => _ResetFormPageState();
}
class _ResetFormPageState extends State<ResetFormPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Username"),
),
TextFormField(
decoration: InputDecoration(labelText: "Email"),
),
Row(
children: [
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print("Form submitted successfully");
}
},
child: Text("Submit"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
_formKey.currentState!.reset();
},
child: Text("Reset"),
),
],
)
],
),
),
),
);
}
}
In this example:
- Clicking Submit runs validation.
- Clicking Reset clears all form fields and resets them to their initial empty state.
Putting It All Together: Full Example
Let’s combine validation and reset into one practical example: a signup form.
class SignupForm extends StatefulWidget {
@override
_SignupFormState createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
void _submitForm() {
if (_formKey.currentState!.validate()) {
print("Form submitted successfully");
}
}
void _resetForm() {
_formKey.currentState!.reset();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Full Name"),
validator: (value) =>
value == null || value.isEmpty ? "Required" : null,
),
TextFormField(
decoration: InputDecoration(labelText: "Email"),
validator: (value) =>
value == null || !value.contains("@") ? "Invalid email" : null,
),
TextFormField(
decoration: InputDecoration(labelText: "Password"),
obscureText: true,
validator: (value) =>
value == null || value.length < 6 ? "Too short" : null,
),
SizedBox(height: 20),
Row(
children: [
ElevatedButton(
onPressed: _submitForm,
child: Text("Submit"),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: _resetForm,
child: Text("Reset"),
),
],
)
],
),
),
),
);
}
}
Best Practices for Using GlobalKey and FormState
- Always use GlobalKey<FormState> for complex forms
- It ensures you can control validation and reset from anywhere in your widget tree.
- Keep validation logic inside validators
- Don’t mix validation with button logic. Keep it inside each
TextFormField.
- Don’t mix validation with button logic. Keep it inside each
- Avoid multiple GlobalKeys unnecessarily
- Use one key per form, not per field.
- Use reset carefully
- Reset clears all fields. Ensure the user doesn’t lose important data unintentionally.
- Combine with TextEditingController when needed
- If you want to prefill values or programmatically modify fields, use both controllers and
FormState.
- If you want to prefill values or programmatically modify fields, use both controllers and
Leave a Reply