A stateful widget is a component that remembers information and can redraw itself.
We’ve already created our first App and know that “everything is a Widget.” But you’ll soon realize there are two types of Widgets, and choosing between them defines how your application behaves.
We’re talking about StatelessWidget and StatefulWidget.
Understanding this difference will allow you to decide where the state should live and which part of the interface needs to be rebuilt.
The concept of state
Before looking at the code, let’s define what State is.
State is the information that can change over time in your Widget and affects how it looks on screen.
- The text of a counter (0, 1, 2…).
- Whether a checkbox is checked or not.
- The color of a button when you press it.
If a piece of data changes and you want the screen to update to reflect that change, that is State.
StatelessWidget
A StatelessWidget is immutable: its properties do not change during the lifetime of that instance. However, Flutter can replace it with another configuration and re-execute its build method.
Think of it as a photograph. Once developed, the photo doesn’t change. If you want the person in the photo to smile, you can’t modify the photo: you have to throw away the old photo and take a new one.
When to use it?
When the part of the interface you are describing does not depend on any user interaction or internally changing data.
- An icon.
- A static text (“App Title”).
- A colored container.
Structure
It’s very simple. We only override the build method.
class MySimpleButton extends StatelessWidget {
// Properties MUST be final (immutable)
final String text;
const MySimpleButton({required this.text});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
color: Colors.blue,
child: Text(text),
);
}
}
Flutter is extremely fast at creating and destroying StatelessWidgets. Don’t be afraid to use and nest them.
StatefulWidget
A StatefulWidget is also immutable, but it delegates the mutable information to a separate State object. This state can change in response to taps, data from the internet, or timers.
Think of it as a video or a robot. It can move, change color, and react.
The two-class architecture
This is where beginners get confused. A StatefulWidget is not a single class; it’s two:
- The Widget: It is immutable (like the Stateless one). It serves only as configuration.
- The State (
State): This is where the logic and mutable data live. This class persists even if the screen is redrawn.
Why two classes? For performance. Flutter can rebuild the UI (the Widget) thousands of times per second, but it keeps the State object in memory so you don’t lose your data (like the counter value).
Structure and setState
Let’s look at the classic counter example. Pay attention to the setState method.
// 1. The Widget class (Public and Immutable)
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
// 2. The State class (Private _ and Mutable)
class _CounterWidgetState extends State<CounterWidget> {
// Here we CAN have variables that are not final
int _counter = 0;
void _increment() {
// setState tells Flutter that something changed
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Clicks: $_counter'),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment, // Call the function on press
child: Icon(Icons.add),
),
);
}
}
The setState() method
When you change the value of _counter++, Dart updates the variable, but the screen doesn’t know about it. The screen still shows “0”.
By wrapping the change inside setState( () { ... } ), you are telling Flutter:
“I have changed a piece of data that affects the interface. Schedule a rebuild of this sub-tree with the updated data.”
If you forget the setState, your variable will change, but the interface will remain frozen.
Which one do I choose?
Here is the golden rule we use:
- Always start with a StatelessWidget. It is lighter and easier to read.
- Do you need that widget to change (color, text, shape) when the user interacts with it? -> Convert it to a StatefulWidget.
- Does the change come from outside (the parent passes it new data)? -> Stay with a StatelessWidget (the parent will handle rebuilding it).