Skip to content

Beginner · Flutter setup and your first app · topic 16 of 50 · 35 min

Stateless and stateful widgets

Explanation

A StatelessWidget only depends on its constructor arguments. A StatefulWidget has a separate State object that keeps data which can change; calling setState tells Flutter to rebuild with the new data. The State lifecycle has initState (set up once), build, didUpdateWidget, and dispose (clean up controllers and subscriptions). After an await, check mounted before calling setState.

Two kinds of widgets

Some parts of the screen never change by themselves: a logo, a label, a card that just shows the data given to it. These are StatelessWidgets: everything they show comes from their constructor arguments. Other parts change in response to the user or to data: a counter, a checkbox, a form, a loading spinner. These need state: data that can change while the widget is on screen. That is what StatefulWidget is for.

The counter, line by line

dart
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _cups = 0;                                  // the state

  void _addCup() {
    setState(() {                                 // tell Flutter the state changed
      _cups++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Chai counter')),
      body: Center(child: Text('Cups sold today: $_cups')),
      floatingActionButton: FloatingActionButton(
        onPressed: _addCup,
        child: const Icon(Icons.add),
      ),
    );
  }
}
Two classes: the widget (immutable, cheap) and its State (long-lived, holds _cups).
  1. CounterPage is the widget. It is immutable and only knows how to create its State.
  2. _CounterPageState holds _cups. It lives as long as this part of the tree stays on screen.
  3. Tapping the button calls _addCup, which changes _cups inside setState.
  4. setState marks the widget as needing a rebuild; Flutter calls build again and the new number appears.
Careful: Changing a field without setState does change the variable, but Flutter is not told, so the screen does not update. Always change displayed state inside setState.

The State lifecycle

MethodWhenUse for
initStateOnce, when the State is createdCreate controllers, start loading data, subscribe to streams
buildEvery time the widget needs to drawDescribe the UI from the current state
didUpdateWidgetWhen the parent rebuilds with new argumentsReact to changed constructor values
disposeOnce, when removed from the tree for goodDispose controllers, cancel subscriptions and timers
dart
class _SearchState extends State<SearchPage> {
  final _controller = TextEditingController();
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(const Duration(seconds: 30), (_) => _refresh());
  }

  Future<void> _refresh() async {
    final results = await fetchResults(_controller.text);   // slow call
    if (!mounted) return;                                   // the user may have left the screen
    setState(() => _results = results);
  }

  @override
  void dispose() {
    _timer?.cancel();
    _controller.dispose();
    super.dispose();
  }
  // ... build and _results omitted
}
Clean up in dispose; check mounted after an await before calling setState.

mounted is true while the State is in the tree. If the user navigates away during a slow network call, calling setState afterwards would throw "setState() called after dispose()"; the mounted check prevents it.

Think of it like this: A StatelessWidget is like a printed poster: it shows what was printed on it. A StatefulWidget is like a scoreboard with an operator (the State) who remembers the score and updates the board each time setState is called. When the match ends (dispose), the operator packs up the equipment.

Which one to choose

  • Start with StatelessWidget. Switch to StatefulWidget only when the widget itself must remember changing data.
  • Keep state as low in the tree as possible, so only a small part rebuilds.
  • When several screens share state, move it into a state management solution (module 7) instead of passing it everywhere.

Common mistakes

  • Changing state without setState: the screen does not update.
  • Calling setState inside build: an infinite rebuild loop or an error.
  • Forgetting dispose for controllers, timers and subscriptions: memory leaks.
  • Calling setState after an await without checking mounted.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Stateless and stateful widgets · 1/6

Stateless = poster Stateful = scoreboard

A stateless widget shows only what it is given. A stateful widget remembers data that changes.

Prefer a video by a teacher? Find free videos on “flutter stateless vs stateful widget setState lifecycle initState dispose” on YouTube ↗

In real life: A tea stall's daily counter

A tea stall owner wants a simple app to count cups sold, with plus and minus buttons and a reset at closing time. A StatefulWidget holds the count; each button calls setState. The total sale (count times the price) is computed in build from the state, so it is always correct. The owner can rotate the phone or switch apps without losing the count during the day.

dart
class _StallState extends State<StallPage> {
  int _cups = 0;
  static const _price = 15;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Cups: $_cups'),
        Text('Sales: Rs ${_cups * _price}'),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(onPressed: _cups == 0 ? null : () => setState(() => _cups--), icon: const Icon(Icons.remove)),
            IconButton(onPressed: () => setState(() => _cups++), icon: const Icon(Icons.add)),
          ],
        ),
      ],
    );
  }
}
The minus button is disabled at zero by passing null.

Practice (0/3 done)

Task 1 · Question

A tap handler runs _count++ without setState. What happens on screen?

Task 2 · Question

Where should a TextEditingController be disposed?

Task 3 · Practice

Build a "water tracker" StatefulWidget: a glass count with + and - buttons (minus disabled at 0), a message that changes at 8 glasses ("Goal reached!"), a reset button, and a Timer started in initState that prints a reminder every 10 seconds and is cancelled in dispose.

Quick quiz

Question 1

1.What does setState do?

Question 2

2.Which method runs once when a State is created?

Question 3

3.Why check mounted after an await?

Question 4

4.When should you choose a StatelessWidget?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does setState do?

Answer: Marks the widget as needing a rebuild after the state changes

Flutter then calls build again.

Which method runs once when a State is created?

Answer: initState

Use it for one-time setup.

Why check mounted after an await?

Answer: The widget may have left the tree, and setState would then throw

Avoids "setState() called after dispose()".

When should you choose a StatelessWidget?

Answer: When everything it shows comes from its constructor arguments

Start stateless by default.

Which object holds a StatefulWidget's State between rebuilds?Related

Answer: The element

Elements are long-lived.

From Interview questions: Flutter

Is setState a bad practice?Related

Answer: No, it is the right tool for ephemeral state

Use it for local state.

From What state is, and setState

What is a widget?Related

Answer: An immutable description of part of the UI

Flutter rebuilds the screen from widgets.

From Everything is a widget

Which API rebuilds a widget only when one value of the model changes?Related

Answer: context.select

select listens to a part.

From Provider and ChangeNotifier

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Flutter setup and your first app”. The module exam comes next.
  2. Module 4 exam: Flutter setup and your first app · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Layouts. 50 topics left in the course before the final exam.

← Previous: Everything is a widget