Stateless vs Stateful Widgets
Two kinds of widgets you write
When you create your own widgets, they’re one of two types, and choosing correctly is fundamental:
- StatelessWidget — for UI that never changes by itself (a label, an icon, a static card). It just builds from the data passed in.
- StatefulWidget — for UI that changes over time in response to user actions or data (a counter, a form, a toggle).
StatelessWidget
class Greeting extends StatelessWidget {
final String name;
const Greeting(this.name, {super.key});
@override
Widget build(BuildContext context) {
return Text('Hi, $name!');
}
}
It receives name and displays it. If name never changes for this widget, stateless is the right, efficient choice.
StatefulWidget
A stateful widget has a companion State class that holds data that can change. When you call setState, Flutter rebuilds the widget with the new values.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0; // this is the state
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $count', style: const TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () => setState(() => count++), // change + rebuild
child: const Text('Add'),
),
],
);
}
}
What setState actually does
setState does two things: it lets you change a state variable, and it tells Flutter “this widget’s data changed — please rebuild it”. Flutter then calls build again with the new value and efficiently repaints only what changed. If you change a variable without setState, the UI won’t update.
The State lifecycle
initState()— called once when the widget is created; set up controllers, listeners, initial data.build()— called every time the UI needs to render.dispose()— called when the widget is removed; clean up controllers and subscriptions here to avoid leaks.
Keep state minimal
Only the data that actually changes belongs in state. Pass everything else in via the constructor. Smaller state means simpler, faster widgets.
Common mistakes
- Changing a variable without
setState(UI doesn’t update). - Using StatefulWidget when Stateless would do (unnecessary complexity).
- Forgetting to
disposecontrollers (memory leaks).
Summary: Use StatelessWidget for fixed UI and StatefulWidget for UI that changes. Store changing data in the State class and update it withsetState, which triggers an efficient rebuild. Clean up indispose.