A StatefulWidget comes in two parts: the widget itself, and a State class that holds the data and the build method.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('You tapped $_count times'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Tap me'),
),
],
);
}
}How setState works
- Change your data inside
setState(() { ... }). - Flutter then runs
buildagain and updates only what changed on the screen. - A name starting with
_, like_count, is private to the file.
Keep state as low in the tree as you can: only the widget that needs the data should own it.
Code examples
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),
);
}
}Expected output
An empty red heart. Tapping it fills the heart; tapping again empties it.
_liked = !_liked flips true and false; setState tells Flutter to redraw the icon.
Common mistakes
Common mistake: Changing a value without setState
_count++ on its own changes the number but the screen stays the same. Wrap the change in setState.
Common mistake: Putting the data in the widget class
Fields in the StatefulWidget class are fixed. Keep changing data in the State class (_CounterState).
Check your understanding
3 quick questions about this lesson.