skip to content

In Flutter, why does calling Theme.of(context) or MediaQuery.of(context) inside initState fail, and where should that code go instead?

level: middleimportance: must knowfreq 66%

answer

  1. initState never runs twice
  2. a dependency that could not refresh
  3. assertion: called before initState completed
  4. didChangeDependencies runs right after
  5. or just read it in build

basics

~20 s

Theme.of and MediaQuery.of register a dependency, and initState runs only once, so a value read there would never refresh; debug builds assert it. Read the value in build, or in didChangeDependencies when derived work is expensive.

solid answer

~40 s

`Theme.of` and `MediaQuery.of` go through `dependOnInheritedWidgetOfExactType` or `dependOnInheritedElement`, which subscribes the State to future changes of that inherited widget. `initState` runs exactly once, so anything computed from such a value there would silently go stale; the framework therefore asserts in debug builds that the lookup 'was called before initState() completed'. Note the `context` itself exists in `initState`, and reading `widget` is fine. The fix is to read inherited data in `build`, which reruns on each change, or in `didChangeDependencies`, which runs immediately after `initState` and again whenever a dependency changes. Use the latter for expensive derived work such as subscribing to a service found through an inherited widget, and compare with the previous value so repeated calls stay cheap.

code

dart · 50 lines
dart
import 'dart:async';

import 'package:flutter/widgets.dart';

class SensorScope extends InheritedWidget {
  const SensorScope({super.key, required this.pressure, required super.child});

  final Stream<double> pressure;

  static Stream<double> of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<SensorScope>()!.pressure;

  @override
  bool updateShouldNotify(SensorScope oldWidget) => pressure != oldWidget.pressure;
}

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

  @override
  State<Barometer> createState() => _BarometerState();
}

class _BarometerState extends State<Barometer> {
  Stream<double>? _source;
  StreamSubscription<double>? _subscription;
  double? _hPa;

  // Calling SensorScope.of(context) in initState would trip the assertion.

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final source = SensorScope.of(context);
    if (source != _source) {
      _subscription?.cancel();
      _source = source;
      _subscription = source.listen((v) => setState(() => _hPa = v));
    }
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Text('${_hPa ?? '-'} hPa');
}

go deeper

for a junior

Recall that inherited lookups like Theme.of belong in build or didChangeDependencies, not in initState.

for a middle

Explain that these lookups register a dependency, that initState never reruns, and that didChangeDependencies runs right after initState and on each change.

for a senior

Spot the workarounds that hide the bug, such as post-frame callbacks or cached theme values, and make didChangeDependencies setup idempotent.

for a principal

Guide teams toward reading ambient data in build and reserving didChangeDependencies for costly derived work, so dependency handling stays predictable across the codebase.

## The rule The `State.initState` documentation says you should not call `BuildContext.dependOnInheritedWidgetOfExactType` from it, and that `didChangeDependencies` is called immediately afterwards and may use it. `Theme.of(context)` calls `dependOnInheritedWidgetOfExactType`, and `MediaQuery.of(context)` goes through `InheritedModel.inheritFrom`, which registers the same kind of dependency. Both are therefore covered by the rule. ## Why it exists An **inherited widget** carries ambient data down the tree. A dependency-registering lookup does two things: returns the current value and records that this element depends on it, so the element is rebuilt when the value changes. That recording is only useful if the code that read the value runs again. `initState` never does: it is called exactly once per State. A theme or media query read there would reflect the first value forever, and a later dark-mode switch or rotation would not reach whatever you computed from it. The framework turns that silent bug into a loud one. In debug builds, `StatefulElement.dependOnInheritedElement` checks the State's lifecycle stage and, while it is still in `initState`, throws an error whose summary reads 'dependOnInheritedWidgetOfExactType<...>() or dependOnInheritedElement() was called before ...initState() completed.' Its hint says references to inherited widgets should occur in `build`, or in `didChangeDependencies`, 'which is called after initState and whenever the dependencies change thereafter.' ## What initState can use - `widget` and its fields: the configuration is set before `initState`. - `context` for things that do not subscribe; the State is attached to its element before `initState`. Which lookups subscribe and which do not is a BuildContext topic. - Creating controllers, timers and subscriptions that depend only on `widget`. ## Where the code goes instead 1. **`build`**: the default. The lookup is a cheap hash-map read on the element, so calling `Theme.of(context)` on every build costs almost nothing, and the value is always fresh. 2. **`didChangeDependencies`**: when the value drives work too expensive to repeat each build, such as a subscription or a network fetch. The framework documents this exact use. It is called right after `initState`, so first-time setup happens there too. 3. **A post-frame callback from `initState`**: this works in the sense of not asserting, but it still reads the value once, after the first frame, and so reintroduces the staleness the rule exists to prevent. ## Making didChangeDependencies safe Because it can run many times, code there must be **idempotent**: - Compare the newly looked-up value with the one you stored, and do nothing if it is the same. - When it differs, release what the old value set up before creating the new one. - Do not call `setState` there; the framework always calls `build` after a dependency change. ## A barometer example A barometer screen gets its pressure stream from a `SensorScope` inherited widget placed high in the tree, so tests and demos can swap the sensor. Subscribing in `initState` would assert. Subscribing in `didChangeDependencies`, guarded by an identity comparison, subscribes once at start, resubscribes if a different `SensorScope` appears above, and is cancelled in `dispose`. ## Summary | Where | Inherited lookup allowed | Reruns on change | |---|---|---| | `initState` | No, debug assertion | No | | `didChangeDependencies` | Yes | Yes | | `build` | Yes | Yes |

  • Why is a post-frame callback scheduled from initState not a real fix?
    It avoids the assertion because the lookup runs after `initState` completed, but it still runs once. The value is a snapshot taken after the first frame, so a later theme or media change never reaches the code that used it, and the first build lacks the value. `didChangeDependencies` runs before the first build and again on every dependency change.
  • didChangeDependencies can run many times; how do you keep expensive work there correct?
    Make it idempotent: store what you derived last time, look the value up again, and act only when it differs, releasing the old subscription or controller before creating the new one. Skip `setState`, because `build` always follows a dependency change.

saying these in an interview costs you the question

  • context does not exist yet inside initState
  • didChangeDependencies runs only once, just like initState
  • Delaying the lookup with a post-frame callback keeps it up to date
  • Theme.of is expensive, so cache it once in initState
  • The assertion also fires in release builds and crashes the app