Flutter Lesson 8 of 83 3 min read
StatefulWidget and setState in Flutter
Learn StatefulWidget in Flutter: the State class, setState, how rebuilding works, and common mistakes to avoid.
On this page
A StatefulWidget can hold data that changes while it is on screen. When the data changes, the widget redraws.
Two classes #
A stateful widget is written as a pair.
import 'package:flutter/material.dart';
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool _liked = false;
int _count = 41;
void _toggle() {
setState(() {
_liked = !_liked;
_count += _liked ? 1 : -1;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: _toggle,
icon: Icon(_liked ? Icons.favorite : Icons.favorite_border),
color: _liked ? Colors.red : null,
),
Text('$_count'),
],
);
}
}
void main() {
runApp(
const MaterialApp(home: Scaffold(body: Center(child: LikeButton()))),
);
}
| Class | Role |
|---|---|
LikeButton | The widget. Immutable, holds configuration, creates the state |
_LikeButtonState | The state. Lives as long as the widget is on screen, holds the changing data and the build method |
The widget object may be thrown away and recreated on every rebuild. The state object survives, which is how the data is remembered.
setState #
setState does two things: it runs your function, which changes the data, and it marks the widget as needing a rebuild. Flutter then calls build again.
setState(() {
_count++;
});
Change a field without setState and the value changes but the screen does not.
Reading the widget’s parameters #
Inside the state class, the widget’s fields are available through widget.
import 'package:flutter/material.dart';
class StepCounter extends StatefulWidget {
const StepCounter({super.key, this.initial = 0, this.step = 1});
final int initial;
final int step;
@override
State<StepCounter> createState() => _StepCounterState();
}
class _StepCounterState extends State<StepCounter> {
late int _value = widget.initial;
@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: () => setState(() => _value += widget.step),
child: Text('Value: $_value'),
);
}
}
Common mistakes #
Calling setState after the widget has gone. After an await, the user may have left the screen. Check mounted first.
Future<void> _load() async {
final data = await fetchData();
if (!mounted) return;
setState(() => _data = data);
}
Doing slow work inside setState. Do the work first, then call setState only to store the result.
Putting state in the widget class. Fields in the StatefulWidget must be final. Changing data belongs in the State class.
One giant stateful widget. setState rebuilds the whole build method of that state. Keep stateful widgets small, so only the part that changes is rebuilt.
Where should state live? #
Put it in the lowest widget that needs it. If two widgets need the same data, move it up to their nearest shared parent and pass it down. This is called lifting state up, and it is the first topic of the state management chapter.
Try it yourself #
Build a ColourSwitcher widget: a square Container that cycles through red, green and blue each time it is tapped. Wrap the container in a GestureDetector with onTap.