Creating Forms and Validation
1. Using Form Widget
| Property | Values | Description |
|---|---|---|
| autovalidateMode | AutovalidateMode.disabled, AutovalidateMode.always, AutovalidateMode.onUserInteraction |
Controls when form validation occurs |
| child | Widget |
The form's child widget, typically containing input fields |
| onChanged | VoidCallback |
Called when any form field changes |
| onWillPop | WillPopCallback |
Called when the user tries to pop the current screen |
Example: Basic Form Setup
class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Form Example')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process form data
}
},
child: Text('Submit'),
),
],
),
),
),
);
}
}
Note: The
GlobalKey<FormState> is essential for accessing form methods like validate() and save().
Warning: Never use
TextFormField without a validator if the field is required. Always provide appropriate validation feedback to users.
2. Implementing Form Validation
| Property | Description | Values |
|---|---|---|
| autovalidateMode | Controls when the form validates its fields | AutovalidateMode.disabled, AutovalidateMode.always, AutovalidateMode.onUserInteraction |
| child | The form widget to be validated | Widget |
Example: Basic Form Validation
Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
],
),
)
Example: Custom Validation Logic
TextFormField(
validator: (value) {
if (value == null || value.length < 5) {
return 'Must be at least 5 characters';
}
return null;
},
)
Example: Email Validation
TextFormField(
validator: (value) {
if (value == null || !RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
)
Note: Use
TextFormField with a validator function to implement validation logic.
Warning: Validation only occurs when the form is submitted or when
autovalidateMode triggers.
3. Saving Form Data
Example: Saving to Local Storage
// Save form data to local storage
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveFormData(Map<String, dynamic> formData) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('formData', jsonEncode(formData));
}
| Property | Description | Usage |
|---|---|---|
| formData | Map containing form field values | Used for persistence and submission |
| SharedPreferences | Local storage mechanism for Flutter | Stores data persistently on device |
Note: Always validate form data before saving to avoid invalid entries.
Warning: Sensitive data should not be stored in SharedPreferences without encryption.
4. Submitting Forms
| Property | Description | Usage |
|---|---|---|
| onSubmit | Callback function triggered when the form is submitted | onSubmit: (formState) => print('Form submitted') |
| formKey | Unique key to identify the form for validation and submission | final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); |
Example: Basic Form Submission
class MyForm extends StatelessWidget {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Form Example')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print('Form submitted');
}
},
child: Text('Submit'),
),
],
),
),
),
);
}
}
Note: The
validate() method must be called on the form's state to ensure all validators pass before submission.
Warning: Never submit sensitive data directly in the UI without proper backend handling and security measures.
5. Using AutovalidateMode
| Property | Values | Description |
|---|---|---|
| autovalidateMode | AutovalidateMode.disabled, AutovalidateMode.always, AutovalidateMode.onUserInteraction |
Determines when form validation occurs |
Example: Setting AutovalidateMode
Form(
autovalidateMode: AutovalidateMode.always,
child: TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter a value';
}
return null;
},
),
)
Note:
AutovalidateMode.always triggers validation on every change, while onUserInteraction only validates after the user interacts with the field.6. Creating Custom Validators
Custom validators in Flutter allow you to define your own validation logic for form fields. They are particularly useful when the built-in validators do not meet your specific requirements.
| Property | Description | Example |
|---|---|---|
| Validator Function | Function that takes a String and returns a String? (error message) or null | |
Example: Custom Email Validator
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}
Example: Using Custom Validator in Form Field
TextFormField(
validator: validateEmail,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
)
Note: Custom validators are applied in the same way as built-in validators. The validator function is called when the form field is validated, and it should return
null if the value is valid or a string error message otherwise.
Key Points
- Custom validators are functions that take a value and return an error message or null
- They provide flexibility to define complex validation logic
- Validators are used in
TextFormFieldor similar widgets with thevalidatorproperty
7. Implementing Cross-Field Validation
Example: Cross-Field Password Confirmation
class PasswordForm extends StatefulWidget {
@override
_PasswordFormState createState() => _PasswordFormState();
}
class _PasswordFormState extends State<PasswordForm> {
final _formKey = GlobalKey<FormState>();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
bool _isPasswordValid(String password) {
return password.length >= 8 && RegExp(r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)').hasMatch(password);
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
validator: (value) {
if (value == null || value.isEmpty) return 'Password is required';
if (!_isPasswordValid(value)) return 'Must be at least 8 chars with uppercase, lowercase, and digit';
return null;
},
),
TextFormField(
controller: _confirmPasswordController,
decoration: InputDecoration(labelText: 'Confirm Password'),
validator: (value) {
if (value == null || value.isEmpty) return 'Confirmation is required';
if (value != _passwordController.text) return 'Passwords do not match';
return null;
},
),
],
),
);
}
}
| Field | Validation Type | Description | Notes |
|---|---|---|---|
| password | Length + Pattern | Minimum 8 characters with at least one uppercase, lowercase, and digit | Uses regex pattern for validation |
| confirmPassword | Equality Check | Must match the password field exactly | Validated against _passwordController.text |
Note: Cross-field validation requires access to multiple form fields' values, typically achieved using controllers or a state management solution.
8. Using FormField Widget
The FormField widget in Flutter is a powerful component that wraps a form field and provides built-in validation capabilities. It allows you to define custom validation logic and integrates seamlessly with the Form widget.
| Property | Description | Values |
|---|---|---|
| builder | Required callback that builds the form field widget. | (FormFieldState<T> state) => Widget |
| validator | Function to validate the field's value. | (T?) => String? |
| initialValue | Initial value for the form field. | T? |
| onSaved | Callback to save the field's value when the form is saved. | (T?) => void |
| onChanged | Callback to handle changes in the field's value. | (T?) => void |
| autovalidateMode | Controls when the field is auto-validated. | AutovalidateMode |
Example: Using FormField with TextFormField
class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<Form>();
String? _email;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
FormField<String>(
builder: (FormFieldState<String> state) {
return TextFormField(
decoration: InputDecoration(
labelText: 'Email',
errorText: state.errorText,
),
onChanged: (value) {
state.didChange(value);
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
);
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print('Form is valid');
}
},
child: Text('Submit'),
),
],
),
);
}
}
Note: The
FormField widget does not provide a built-in UI for the field. You must define the UI using the builder callback.
Key Features of FormField
- Supports custom validation logic.
- Integrates with the
Formwidget for validation and saving. - Allows access to the
FormFieldStatefor managing field state. - Enables custom UI rendering through the
buildercallback.
9. Resetting Forms
| Property | Description | Usage |
|---|---|---|
| FormState.reset() | Resets all form fields to their initial values | Call on a FormState instance to clear all fields |
Example: Resetting a Form
final formKey = GlobalKey<FormState>();
// Reset the form
formKey.currentState!.reset();
Note: The
reset() method only resets the form fields to their initial values. It does not reset any validation or error states.
10. Managing Form State and Focus Navigation
| Property | Description | Values |
|---|---|---|
| autovalidateMode | Controls when the form validates its fields | AutovalidateMode.disabled, AutovalidateMode.always, AutovalidateMode.onUserInteraction |
| child | The form's child widget | Widget |
| onChanged | Callback when any field in the form changes | VoidCallback |
| onSaved | Callback when the form is saved | VoidCallback |
| validator | Function to validate the form's value | FormValidator |
Example: Form with Validation
class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a name';
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
],
),
);
}
}
Example: Focus Navigation
class FocusForm extends StatefulWidget {
@override
_FocusFormState createState() => _FocusFormState();
}
class _FocusFormState extends State<FocusForm> {
final TextEditingController _nameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
FocusNode _nameFocusNode = FocusNode();
FocusNode _emailFocusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Column(
children: [
TextFormField(
controller: _nameController,
focusNode: _nameFocusNode,
decoration: InputDecoration(labelText: 'Name'),
),
TextFormField(
controller: _emailController,
focusNode: _emailFocusNode,
decoration: InputDecoration(labelText: 'Email'),
),
ElevatedButton(
onPressed: () {
if (_nameFocusNode.hasFocus) {
FocusScope.of(context).requestFocus(_emailFocusNode);
} else if (_emailFocusNode.hasFocus) {
FocusScope.of(context).unfocus();
}
},
child: Text('Next Field'),
),
],
);
}
}
11. Creating Reusable Form Components
Example: Custom Text Input Field
class CustomTextInput extends StatelessWidget {
final String label;
final TextEditingController controller;
final String? Function(String?)? validator;
const CustomTextInput({
Key? key,
required this.label,
required this.controller,
this.validator,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
TextFormField(
controller: controller,
validator: validator,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
],
);
}
}
| Property | Description | Values |
|---|---|---|
| label | Label text for the input field | String |
| controller | Controller to manage the input value | TextEditingController |
| validator | Validation function for the input field | String? Function(String?)? |
Note: The
validator function should return null if the input is valid, or an error message string if it's invalid.
Example: Reusable Form Field with Validation
class ValidatedFormField extends StatelessWidget {
final String label;
final TextEditingController controller;
final String? Function(String?) validator;
const ValidatedFormField({
Key? key,
required this.label,
required this.controller,
required this.validator,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
TextFormField(
controller: controller,
validator: validator,
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: null, // Error handling is managed by Form
),
),
],
);
}
}