State is data that changes over time. Flutter gives you several ways to manage it.
setState (Basic)
Dart
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool liked = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(liked ? Icons.favorite : Icons.favorite_border),
color: Colors.red,
onPressed: () => setState(() => liked = !liked),
);
}
}TextEditingController
Dart
class NameForm extends StatefulWidget {
const NameForm({super.key});
@override
State<NameForm> createState() => _NameFormState();
}
class _NameFormState extends State<NameForm> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose(); // Always dispose controllers!
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: controller,
decoration: InputDecoration(
labelText: 'Enter your name',
border: OutlineInputBorder(),
),
),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Hello, ${controller.text}!')),
);
},
child: Text('Submit'),
),
],
);
}
}FutureBuilder (Async Data)
Dart
class UserList extends StatelessWidget {
const UserList({super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: fetchUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
final users = snapshot.data!;
return Column(
children: users.map((u) => ListTile(title: Text(u))).toList(),
);
},
);
}
}When to Use What
| Approach | Use when |
|---|---|
setState |
Simple local state (toggle, counter, form) |
FutureBuilder |
One-shot async data (API call) |
StreamBuilder |
Continuous data (WebSocket, real-time) |
| Provider / Riverpod | Shared state across many widgets |
| BLoC | Complex business logic with streams |
Tips
- Call
dispose()on controllers and streams to prevent memory leaks. - Keep state as close to where it's used as possible.
- Use
constconstructors when possible. - Don't put all state in a single parent — distribute it to child widgets.
Next: navigation.