build and BuildContext
The build contract
Section titled “The build contract”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.
@overrideWidget build(BuildContext context) { // ✅ Good: just describe UI from current state. return Text('Count: $_count');}@overrideWidget 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.
What BuildContext actually is
Section titled “What BuildContext actually is”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.
@overrideWidget 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
The classic context pitfall
Section titled “The classic context pitfall”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.