skip to content

In Flutter, when does a State's didUpdateWidget run versus didChangeDependencies, and what work belongs in each?

level: middleimportance: should knowfreq 55%

answer

  1. parent configuration versus ambient data
  2. same runtimeType and same key
  3. oldWidget is the argument
  4. build always follows both
  5. setState in them is redundant

basics

~20 s

didUpdateWidget(oldWidget) runs when the parent rebuilds with a new same-type, same-key widget; compare old and new fields and swap subscriptions there. didChangeDependencies runs after initState and whenever a depended-on inherited widget changes. Build follows both.

solid answer

~40 s

The two callbacks react to two different sources of change. `didUpdateWidget(oldWidget)` runs when the parent rebuilds and puts a new widget with the same `runtimeType` and key at this position; by then `widget` already refers to the new instance, and the argument is the previous one, so you compare fields and, for example, cancel the old stream subscription and subscribe to the new stream. `didChangeDependencies` runs immediately after `initState` and whenever an inherited widget this State registered a dependency on changes, or the State moves in the tree while holding dependencies; it is for work derived from inherited data. In both, the framework calls `build` afterwards, so `setState` there is redundant, neither may be `async`, and both must call `super` first.

code

dart · 10 lines
dart
// Inside _BarometerState; Barometer has final Stream<double> readings and String label.
@override
void didUpdateWidget(Barometer oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.readings != widget.readings) {
    _subscription?.cancel();
    _subscription = widget.readings.listen((v) => setState(() => _hPa = v));
  }
  // A changed label needs nothing here: build reads widget.label.
}

go deeper

for a junior

Recall that didUpdateWidget reacts to a new configuration from the parent and didChangeDependencies to inherited data, and that build follows both.

for a middle

Explain the triggers precisely, what oldWidget holds, why setState there is redundant, and why didChangeDependencies also runs after initState.

for a senior

Design resource handling around them: compare fields before swapping subscriptions or controllers, and keep both callbacks idempotent and synchronous.

for a principal

Decide when a widget should take a resource as a parameter versus find it through an inherited scope, since that choice decides which callback owns the swap.

## Two sources of change A `State` can need to react to two kinds of change besides its own `setState`: - **Its configuration changed**: the parent rebuilt and handed a new widget instance with different field values. This is **didUpdateWidget**. - **Ambient data changed**: an inherited widget above it, such as `Theme` or a custom scope, now carries a different value. This is **didChangeDependencies**. Confusing them is a classic middle-level interview slip, because both lead to a rebuild. ## didUpdateWidget(oldWidget) The `State` documentation describes it precisely: if the parent rebuilds and requests that this location display a new widget with the same `runtimeType` and `Widget.key`, the framework updates the `widget` property to the new widget and then calls `didUpdateWidget` with the previous widget as the argument. What belongs there: 1. Compare `oldWidget.x` with `widget.x` for each field that drives a resource. 2. When a resource-driving field changed, release the old resource and acquire the new one: cancel and resubscribe a stream, update a controller's duration, restart an implicit animation. 3. Leave fields that only affect rendering alone; `build` reads them from `widget` anyway. It runs on every parent rebuild that updates this element, whether or not any field changed, so the comparison is essential. It must start with `super.didUpdateWidget(oldWidget)`, and returning a `Future` triggers the debug assertion 'didUpdateWidget() returned a Future.' ## didChangeDependencies It is called immediately after `initState`, and later whenever an inherited widget that a previous lookup depended on changes, or when the State is reinserted at a new position while it had dependencies. The framework calls it just before the rebuild it causes. The documentation notes that subclasses rarely override it, because `build` runs after a dependency change anyway; override it only for work too expensive to do on every build, such as a network fetch or a subscription derived from inherited data. ## Side by side | | `didUpdateWidget` | `didChangeDependencies` | |---|---|---| | Trigger | Parent supplies a new same-type, same-key widget | After `initState`; inherited dependency changed; moved with dependencies | | Argument | The old widget | None | | Runs on first insertion | No | Yes | | Typical work | Swap a subscription for a changed stream parameter | Resubscribe to a service found through an inherited widget | | `build` afterwards | Always | Always | ## Barometer example A `Barometer` takes a `readings` stream and a `label` from its parent. When the parent passes a different stream, `didUpdateWidget` sees `oldWidget.readings != widget.readings`, cancels the old subscription and listens to the new stream. When only the label changes, `didUpdateWidget` does nothing, because `build` reads `widget.label`. If the barometer instead found its stream through an inherited `SensorScope`, the same swap would live in `didChangeDependencies`. ## Common mistakes - Calling `setState` in either callback: harmless but redundant, since `build` follows. - Resubscribing on every `didUpdateWidget` call without comparing, which drops and recreates the subscription on each parent rebuild. - Reading the argument as the new widget: `oldWidget` is the previous configuration; the new one is `widget`. - Expecting `didUpdateWidget` to fire when an inherited value changes, or `didChangeDependencies` to fire when the parent passes new arguments.

  • Why must didUpdateWidget compare oldWidget with widget before resubscribing?
    Because it runs on every parent rebuild that updates this element, even when the relevant field is unchanged. Resubscribing unconditionally cancels and recreates the subscription each time, which can drop events, restart a single-subscription source that cannot be listened to twice, and waste work. Compare the field that drives the resource and act only when it changed.
  • What happens if you mark didUpdateWidget async to await some setup?
    Debug builds report 'didUpdateWidget() returned a Future.', because the framework calls it synchronously and builds immediately afterwards without waiting. Start the asynchronous work from a separate method without awaiting it, and update state with `setState` when it completes, after checking that the State is still mounted.

saying these in an interview costs you the question

  • didUpdateWidget runs when an inherited widget like Theme changes
  • Call setState in didUpdateWidget so the new values appear
  • didChangeDependencies runs whenever the parent rebuilds
  • didUpdateWidget receives the new widget as its argument
  • Resubscribe unconditionally on every didUpdateWidget call