Flutter Forms and User Input
Forms collect information from users — login details, signup data, search queries. Flutter provides a powerful Form widget with built-in validation to make this process clean and reliable.
The TextField Widget
TextField is the basic text input widget. It shows a keyboard and accepts user typing.
TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
print('User typed: $value');
},
)
Keyboard Types
TextInputType.text → Standard keyboard TextInputType.emailAddress → Shows @ and .com TextInputType.number → Number pad TextInputType.phone → Phone pad TextInputType.multiline → Multi-line text area
Reading TextField Value with TextEditingController
To read what the user typed, attach a TextEditingController to the field.
class LoginScreen extends StatefulWidget {
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
void dispose() {
// Always dispose controllers to free memory
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _login() {
String email = _emailController.text;
String password = _passwordController.text;
print('Email: $email, Password: $password');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
obscureText: true, // Hides password characters
decoration: InputDecoration(labelText: 'Password'),
),
ElevatedButton(onPressed: _login, child: Text('Login')),
],
);
}
}
Form Widget with Validation
The Form widget wraps multiple TextFormField widgets and validates them all at once with a single method call.
Form (validator coordinator) └── TextFormField (email, with validator) └── TextFormField (password, with validator) └── ElevatedButton (calls form.validate())
class SignupForm extends StatefulWidget {
@override
State<SignupForm> createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
void _submit() {
if (_formKey.currentState!.validate()) {
// All fields passed validation
print('Form is valid!');
print('Name: ${_nameController.text}');
print('Email: ${_emailController.text}');
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Full Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null; // null means valid
},
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Enter a valid email address';
}
return null;
},
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _submit,
child: Text('Sign Up'),
),
],
),
);
}
}
Validation Flow Diagram
User taps "Sign Up"
↓
_formKey.currentState!.validate() called
↓
Each TextFormField runs its validator()
↓
┌──────────────────────────────────┐
│ validator returns null? │
│ YES → field is valid ✓ │
│ NO → show error message ✗ │
└──────────────────────────────────┘
↓
All fields valid? → run _submit logic
Any field invalid? → show errors, stop
Other Input Widgets
Checkbox
bool _agreed = false;
Checkbox(
value: _agreed,
onChanged: (value) {
setState(() { _agreed = value!; });
},
)
Switch
bool _darkMode = false;
Switch(
value: _darkMode,
onChanged: (value) {
setState(() { _darkMode = value; });
},
)
DropdownButton
String _selectedCity = 'Mumbai';
DropdownButton<String>(
value: _selectedCity,
items: ['Mumbai', 'Delhi', 'Bangalore']
.map((city) => DropdownMenuItem(value: city, child: Text(city)))
.toList(),
onChanged: (value) {
setState(() { _selectedCity = value!; });
},
)
Slider
double _rating = 3;
Slider(
value: _rating,
min: 1,
max: 5,
divisions: 4,
label: _rating.toString(),
onChanged: (value) {
setState(() { _rating = value; });
},
)
Showing a Snackbar After Form Submit
A Snackbar is a brief message that appears at the bottom of the screen after an action.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Signup successful!'),
backgroundColor: Colors.green,
duration: Duration(seconds: 3),
),
);
Best Practices for Forms
- Always dispose
TextEditingControllerobjects indispose(). - Use
Formwith aGlobalKeyfor multi-field validation. - Set
obscureText: trueon password fields. - Use the correct
keyboardTypeto make entry easier for users. - Show clear, friendly error messages — never technical jargon.
