StatelessWidget vs StatefulWidget
The two base widget classes, what "state" means in Flutter, and how to decide which one a widget needs.
읽는 데 2분
Every widget you write extends one of two classes: StatelessWidget or StatefulWidget. Choosing correctly between them is one of the first real decisions you make when building a Flutter screen.
StatelessWidget: no memory of its own
A StatelessWidget describes UI based only on the configuration it's given (its constructor parameters) and nothing else. Once built, it never changes itself — if it needs to look different, that has to come from a parent passing it new configuration.
class PriceTag extends StatelessWidget {
final double amount;
const PriceTag({super.key, required this.amount});
@override
Widget build(BuildContext context) {
return Text('\$${amount.toStringAsFixed(2)}');
}
}PriceTag has no way to change its own amount — it just renders whatever it was given. This makes stateless widgets predictable and cheap: given the same input, build always produces the same output.
StatefulWidget: a widget that can change itself
A StatefulWidget is for anything that needs to hold information that changes over time in response to user interaction, a timer, a network response, and so on — a checkbox being toggled, a counter incrementing, a form field being typed into.
A StatefulWidget is actually two classes working together: the widget itself (immutable, like before) and a separate State object that Flutter keeps alive across rebuilds.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Count: $_count'),
ElevatedButton(onPressed: _increment, child: const Text('Add')),
],
);
}
}The _count field lives on _CounterState, not on Counter. That distinction matters: Counter itself gets thrown away and recreated constantly as Flutter rebuilds the tree, but Flutter preserves the associated _CounterState object across those rebuilds, so _count survives.
Why setState is the trigger
Calling setState does two things: it runs the code inside the callback (here, incrementing _count), and it tells Flutter "this widget's output may have changed — please call build again." Without setState, changing _count directly would update the variable but never repaint the screen, because Flutter would have no idea anything changed.
_count++; // Wrong: updates the value, but the screen never updates.
setState(() {
_count++; // Right: updates the value AND schedules a rebuild.
});Choosing between them
A useful rule of thumb: start every widget as a StatelessWidget. Only convert it to a StatefulWidget when you find yourself needing a mutable field that survives across rebuilds and changes in response to something happening inside the widget itself. Data that's handed down from a parent and never changes locally almost never needs StatefulWidget — it's tempting to reach for state by default, but the fewer stateful widgets you have, the easier your app is to reason about.