Intermediate · Lists, navigation and forms · topic 26 of 50 · 35 min
Forms and validation
Explanation
Why Form?
Sign-up, address, payment and feedback screens all collect input. You need to check it (validation), show clear errors next to the right field, and only submit when everything is correct. Flutter's Form widget groups fields so you can validate, save or reset them together.
class SignupForm extends StatefulWidget {
const SignupForm({super.key});
@override
State<SignupForm> createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final _formKey = GlobalKey<FormState>();
final _name = TextEditingController();
final _mobile = TextEditingController();
@override
void dispose() {
_name.dispose();
_mobile.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Welcome, ${_name.text.trim()}!')),
);
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _name,
decoration: const InputDecoration(labelText: 'Full name'),
validator: (v) => (v == null || v.trim().length < 2) ? 'Enter your name' : null,
),
TextFormField(
controller: _mobile,
decoration: const InputDecoration(labelText: 'Mobile number', prefixText: '+91 '),
keyboardType: TextInputType.phone,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10)],
validator: validateMobile,
),
const SizedBox(height: 16),
FilledButton(onPressed: _submit, child: const Text('Create account')),
],
),
);
}
}Validators
A validator is a function that receives the current text and returns null if it is valid, or an error message string if not. Keep validators as small, plain functions so you can unit test them (module 10).
String? validateMobile(String? value) {
final v = value?.trim() ?? '';
if (v.isEmpty) return 'Mobile number is required';
if (v.length != 10) return 'Enter 10 digits';
if (!RegExp(r'^[6-9]').hasMatch(v)) return 'Indian mobile numbers start with 6, 7, 8 or 9';
return null;
}
String? validatePin(String? value) =>
RegExp(r'^[1-9][0-9]{5}$').hasMatch(value ?? '') ? null : 'Enter a valid 6-digit PIN code';Controllers
A TextEditingController holds a field's text: read it with controller.text, set it with controller.text = '...', clear it with clear(), and listen for changes with addListener. Controllers hold resources, so create them in the State (as fields or in initState) and dispose them in dispose.
You do not always need a controller. TextFormField also has onChanged (called on every keystroke) and onSaved (called by formKey.currentState!.save()), which are enough for many forms. Use a controller when you need to set the text from code, clear it, or read it outside the field.
Keyboard and input settings
| Setting | Effect |
|---|---|
| keyboardType: TextInputType.phone / number / emailAddress | Shows the right keyboard |
| obscureText: true | Hides password characters |
| textInputAction: TextInputAction.next / done | The keyboard's action button moves to the next field or finishes |
| inputFormatters | Blocks unwanted characters or limits length as the user types |
| autovalidateMode: AutovalidateMode.onUserInteraction | Re-validates a field after the user starts editing it |
Remember to import package:flutter/services.dart for input formatters. Formatters stop bad input early; validators explain what is still wrong.
Good form UX
- Show errors after the user has interacted or tried to submit, not on a fresh form.
- Disable the submit button or show a progress indicator while submitting, to avoid double submissions.
- Keep the form scrollable (SingleChildScrollView or ListView) so the keyboard does not hide fields on small phones.
- Validate on the server too: anyone can bypass app checks.
Common mistakes
- Forgetting the key on Form, so currentState is null.
- Not disposing TextEditingControllers: memory leaks.
- Validators that throw or return empty strings: return null for valid, a message for invalid.
- A non-scrollable form that the keyboard covers on small phones.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Forms and validation · 1/6
Wrap fields in a Form with a global key of form state.
Prefer a video by a teacher? Find free videos on “flutter form validation textformfield globalkey controller input formatters” on YouTube ↗
In real life: A delivery address form
A grocery app's address form has name, mobile, PIN code and address line fields. Each has a clear validator; the PIN field accepts only digits and at most 6 of them; pressing Next on the keyboard moves to the next field. The Save button validates everything and only then saves the address, so the delivery partner never receives a 5-digit PIN.
TextFormField(
decoration: const InputDecoration(labelText: 'PIN code'),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)],
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validatePin,
)Practice (0/3 done)
What should a validator return for valid input?
_formKey.currentState is null when you call validate(). What is the likely cause?
Build a signup form with name, email, mobile, password and confirm password. Write each validator as a separate function and test them in main or unit tests: email must contain @ and a dot after it, mobile 10 digits starting with 6-9, password at least 8 characters with a number, confirm must match. Make the form scrollable and dispose all controllers.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Navigator basicsLists, navigation and forms · same module
- Grids with GridViewLists, navigation and forms · same module
- Text input and formsKotlin and Android App Development · Jetpack Compose UI
- Forms and client-side validationFull-Stack Development · Building the frontend
- Built-in form validationFrontend Web Development · Forms, semantic HTML and accessibility
- Forms with JavaScriptFrontend Web Development · JavaScript in the browser: DOM and events
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which type of key does a Form use for validation?
Answer: GlobalKey<FormState>
GlobalKey gives access to the FormState.
Which setting hides password characters?
Answer: obscureText: true
obscureText shows dots.
Which formatter allows only digits?
Answer: FilteringTextInputFormatter.digitsOnly
From flutter/services.dart.
Why must the server validate input again?
Answer: App-side checks can be bypassed
Never trust only the client.
Why must the server validate input again?Related
The form already checks that the title is 1 to 100 characters. Why must the server check again?Related
Answer: Requests can be sent without the form, for example with curl or edited code
Browser checks can be skipped. Only the server check protects the data.
Which attribute stops a form from being sent while a field is empty?Related
Answer: required
required makes an empty field invalid, so the browser stops the submit.
Which attribute must a field have so that new FormData(form) includes it?Related
Answer: name
FormData uses the name attribute as the key. A field without a name is left out.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Lists, navigation and forms”. The module exam comes next.
- Module 6 exam: Lists, navigation and forms · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, State management. 50 topics left in the course before the final exam.