Intermediate · State management · topic 27 of 50 · 30 min
What state is, and setState
Explanation
Two kinds of state
The Flutter docs divide state into two kinds. Ephemeral state (also called UI or local state) lives inside one widget and nobody else cares about it: the current tab of a TabBar, whether a password field shows its text, the progress of an animation. App state is shared across many parts of the app and often needs to survive moving between screens: the shopping cart, the logged-in user, notification settings, a list of favourites.
| Example | Kind | Good tool |
|---|---|---|
| Is the password visible? | Ephemeral | setState in a StatefulWidget |
| Selected tab on one screen | Ephemeral | setState |
| Items in the cart (shown on many screens) | App state | Provider, Riverpod or Bloc |
| Logged-in user and token | App state | Provider, Riverpod or Bloc (plus secure storage) |
Lifting state up
When two sibling widgets need the same data, move the state to their closest common parent. The parent owns the data and passes it down as constructor arguments; children report changes upward through callbacks. This "lifting state up" keeps one source of truth.
class CartPage extends StatefulWidget {
const CartPage({super.key});
@override
State<CartPage> createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
int _qty = 1; // state lives in the parent
@override
Widget build(BuildContext context) {
return Column(
children: [
QuantityPicker(qty: _qty, onChanged: (q) => setState(() => _qty = q)),
PriceSummary(total: _qty * 120),
],
);
}
}
class QuantityPicker extends StatelessWidget {
const QuantityPicker({super.key, required this.qty, required this.onChanged});
final int qty;
final ValueChanged<int> onChanged; // callback up to the parent
@override
Widget build(BuildContext context) => Row(children: [
IconButton(onPressed: qty > 1 ? () => onChanged(qty - 1) : null, icon: const Icon(Icons.remove)),
Text('$qty'),
IconButton(onPressed: () => onChanged(qty + 1), icon: const Icon(Icons.add)),
]);
}The problem: prop drilling
Now imagine the cart count must appear in the AppBar of every screen, the checkout button, and a badge on the bottom navigation bar. Passing cart data and callbacks through every widget in between, even ones that do not use it, is called prop drilling. It makes code long, fragile and hard to change.
InheritedWidget: the foundation
Flutter solves this with InheritedWidget: a widget placed high in the tree that any descendant can read directly through BuildContext, without passing it down by hand. You already use it: Theme.of(context) and MediaQuery.of(context) read InheritedWidgets. Widgets that read it are rebuilt automatically when it changes. Writing InheritedWidgets by hand is verbose, so packages such as Provider wrap it in a friendlier API.
Common mistakes
- Putting all state in the top-level widget "just in case": every change rebuilds the whole app.
- Using a state management package for ephemeral state like a checkbox. setState is perfect there.
- Keeping two copies of the same data in different widgets, which get out of sync.
- Passing callbacks through five levels of widgets instead of using a shared state tool.
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
What state is, and setState · 1/6
State is data that changes and affects the screen.
Prefer a video by a teacher? Find free videos on “flutter state management explained ephemeral vs app state lifting state up” on YouTube ↗
In real life: A product page with a quantity picker
On a clothing app's product page, the quantity picker and the price summary both need the quantity. The developer lifts the quantity to the product page's State and passes it down, with an onChanged callback up. When the cart count must also appear on the home screen's bottom bar, she moves the cart to Provider (next topic), keeping setState only for page-local things like the selected size.
Practice (0/3 done)
Which is ephemeral state?
Two sibling widgets need the same counter. What is the simplest correct approach?
Build a "split the bill" screen: a parent StatefulWidget holds the total and number of people; a stateless PeoplePicker changes the number through a callback; a stateless ShareDisplay shows each person's share. Then list which state in your own app ideas is ephemeral and which is app state.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Provider and ChangeNotifierState management · same module
- Riverpod overviewState management · same module
- Bloc, Cubit and choosing an approachState management · same module
- State: what the screen remembersFull-Stack Development · Building the frontend
- Props and compositionFrontend Web Development · React concepts
- State and eventsFrontend Web Development · React concepts
Questions people ask about this
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 is app state?
Answer: State shared by many parts of the app, like a cart
It usually survives navigation.
What is prop drilling?
Answer: Passing data through many widget levels that do not use it
Shared state tools avoid it.
Which built-in mechanism do Theme.of and MediaQuery.of use?
Answer: InheritedWidget
Descendants read inherited data via context.
Is setState a bad practice?
Answer: No, it is the right tool for ephemeral state
Use it for local state.
SearchBar shows the search text and NoteList filters by it. Where should the search text state live?Related
Answer: In NotesPage, their nearest common parent
Lifting state up to the nearest common parent gives one source of truth, passed down as props.
What does setState do?Related
Answer: Marks the widget as needing a rebuild after the state changes
Flutter then calls build again.
How do you pass the number 90 as the price prop?Related
Answer: price={90}
Curly braces pass a JavaScript value. price="90" would pass the string "90", and price=90 is not valid JSX.
A ProductList and a CartBadge (siblings) both need the cart. Where should the cart state live?Related
Answer: In their closest common parent, passed down as props
Lift state up to the closest common parent, then pass the value and the change functions down. Two copies would get out of sync.
From State and events
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
- 3 more topics in “State management” before its exam: Provider and ChangeNotifier, Riverpod overview, Bloc, Cubit and choosing an approach.
- Module 7 exam: State management · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Networking, storage and Firebase. 50 topics left in the course before the final exam.