Skip to content

build and BuildContext

build has one job: return a widget tree that describes the UI for the current state. It should be a pure, fast function of the widget’s fields and state — nothing more.

The rule that matters: build can be called very often — every frame of an animation, every setState, every time an ancestor rebuilds. So build must be cheap and free of side effects.

@override
Widget build(BuildContext context) {
// ✅ Good: just describe UI from current state.
return Text('Count: $_count');
}
@override
Widget build(BuildContext context) {
// ❌ Bad: side effects in build. This fires on EVERY rebuild.
_apiClient.fetchUser(); // network call — leaks, fires repeatedly
_controller = AnimationController(vsync: this); // recreated every build!
return Text('...');
}

Side effects — network calls, creating controllers, subscribing to streams — belong in initState (once) or event handlers, never in build. Treat build as read-only rendering.

Every build receives a BuildContext. It is not a bag of globals — it is a handle to this widget’s location in the element tree. (In fact, BuildContext is the element.) That location is what lets a widget look up the tree to find inherited data.

@override
Widget build(BuildContext context) {
// context knows WHERE we are, so it can search ancestors:
final theme = Theme.of(context); // nearest Theme above us
final media = MediaQuery.of(context); // nearest MediaQuery above us
return Text('Screen is ${media.size.width} wide', style: theme.textTheme.bodyMedium);
}

Theme.of(context) walks up from this element to find the nearest Theme ancestor. This is why context matters: the same .of(context) call returns different results depending on where in the tree the widget sits.

flowchart BT
  here["this widget
(context = its element)"] --> p1["Padding"]
  p1 --> p2["Theme (found!)"]
  p2 --> root["MaterialApp"]
  here -.->|"Theme.of(context)
searches upward"| p2
context.of walks up the element tree to find an ancestor

Because .of(context) searches upward, a common bug is using a context that sits above the widget you’re looking for. For example, calling Scaffold.of(context) with the context of the same build method that created the Scaffold fails — that context is above the Scaffold, not below it. The fix is a Builder (or a child widget) so you get a context located under the target.

Why must the build method be free of side effects?
Where should you create an AnimationController or start a network request?
What is a BuildContext?
What does `Theme.of(context)` do?