Forms, Input & Validation
TextField and controllers
The basic input widget is TextField. To read or change its value you usually attach a TextEditingController.
final nameController = TextEditingController();
TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Name',
border: OutlineInputBorder(),
),
)
// read the value later:
print(nameController.text);
Always dispose() controllers in your State’s dispose method to avoid leaks.
The Form widget for validation
For multiple fields with validation, wrap them in a Form with a GlobalKey and use TextFormField, which adds a validator.
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(children: [
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Enter a valid email';
}
return null; // null means valid
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// all validators passed -> submit
}
},
child: const Text('Submit'),
),
]),
)
validate() runs every field’s validator and shows error messages automatically — a validator returns null when valid, or an error string when not.
Input types and formatting
TextFormField(
keyboardType: TextInputType.emailAddress,
obscureText: true, // for passwords
inputFormatters: [FilteringTextInputFormatter.digitsOnly], // numbers only
)
Managing focus
Use FocusNode and textInputAction to move from one field to the next when the user taps the keyboard’s “next” button — a small touch that greatly improves form UX.
Common mistakes
- Forgetting to
disposecontrollers (memory leaks). - Not wrapping fields in a
Formwhen you need validation. - Setting the wrong
keyboardType(e.g. numbers vs email).
Summary: UseTextField+TextEditingControllerfor input, and aFormwithTextFormFieldvalidators for multi-field validation (validate()checks them all). Pick the rightkeyboardTypeand dispose controllers.