skip to content

Inherited Data Propagation

InheritedWidget pushes data down the tree and rebuilds only the widgets that registered a dependency. Interviewers ask why MediaQuery.of rebuilds too much and how aspects narrow it.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Flutter, what problem does an InheritedWidget solve, and how do you declare one so descendants can read its data?

level: juniorimportance: must knowfreq 62%

answer

  1. avoid threading a value through constructors
  2. subclass with a final field
  3. static of and maybeOf
  4. dependOnInheritedWidgetOfExactType registers
  5. updateShouldNotify compares old widget

basics

~20 s

An InheritedWidget makes a value available to every descendant without passing it through each constructor. You subclass it with final fields, add static of/maybeOf methods that register a dependency, and override updateShouldNotify so only registered dependents rebuild when the value changes.

solid answer

~40 s

It solves **prop drilling**: a value such as the app's unit system (metric or imperial) is needed deep in the tree, and threading it through every intermediate constructor couples widgets that never use it. You declare a subclass of `InheritedWidget` with `final` fields and a `child`, plus the conventional static `of(context)` / `maybeOf(context)` methods that call `context.dependOnInheritedWidgetOfExactType<T>()`. That call both returns the nearest ancestor of exactly that type and **registers** the calling element as a dependent. You override `updateShouldNotify(oldWidget)` to say whether a new instance carries changed data; when it returns `true`, the framework rebuilds only the registered dependents, not the whole subtree. The widget itself is immutable, so the value changes when something above it (usually a `StatefulWidget`) rebuilds it with new data.

code

dart · 36 lines
dart
import 'package:flutter/widgets.dart';

enum UnitSystem { metric, imperial }

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

  final UnitSystem units;

  static UnitSystem? maybeOf(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<UnitsScope>()?.units;

  static UnitSystem of(BuildContext context) {
    final UnitSystem? units = maybeOf(context);
    assert(units != null, 'No UnitsScope found in context');
    return units!;
  }

  @override
  bool updateShouldNotify(UnitsScope oldWidget) => units != oldWidget.units;
}

class TrailDistance extends StatelessWidget {
  const TrailDistance({super.key, required this.km});

  final double km;

  @override
  Widget build(BuildContext context) {
    final UnitSystem units = UnitsScope.of(context);
    return Text(switch (units) {
      UnitSystem.metric => '${km.toStringAsFixed(1)} km',
      UnitSystem.imperial => '${(km * 0.621371).toStringAsFixed(1)} mi',
    });
  }
}

go deeper

for a junior

Recall the three parts: final data plus child, static of and maybeOf that call dependOnInheritedWidgetOfExactType, and updateShouldNotify comparing the old widget's data.

for a middle

Explain that the lookup registers the element as a dependent, that only dependents rebuild when updateShouldNotify returns true, and that a StatefulWidget above supplies new instances.

for a senior

Show you keep the scope's child instance stable so only dependents rebuild, and that you catch in-place mutation of scoped data during review.

for a principal

Weigh a hand-written inherited scope against a state-management package for app-wide settings: fewer dependencies and full control versus lifecycle and testing conveniences.

## The problem: data needed far from where it lives In a hiking-trail app, the user picks a **unit system** once in settings: metric or imperial. That choice matters to a trail card's distance label, an elevation chart's axis, a pace field on the recording screen and a weather tile, all several levels below the screen that owns the setting. Without a sharing mechanism you pass `units` through every intermediate constructor. That is **prop drilling**, and it has three costs: - every widget on the path gains a parameter it never reads; - adding one more consumer means editing every ancestor on its path; - when the value changes, the whole path rebuilds, because each constructor argument changed. `InheritedWidget` is Flutter's built-in answer. It is the primitive under `Theme`, `MediaQuery`, `Directionality`, `DefaultTextStyle` and many package-level scopes. ## How you declare one An `InheritedWidget` subclass has three parts: 1. **Immutable data** in `final` fields, plus the required `child` passed to the super constructor. 2. **Static accessors** by convention: `maybeOf(context)` returns `null` when no ancestor exists; `of(context)` asserts or throws with a helpful message instead. Both call `context.dependOnInheritedWidgetOfExactType<T>()`. 3. **`updateShouldNotify(oldWidget)`**, which the framework calls when the widget at this position is replaced by a new instance. It receives the previous widget, which is guaranteed to have the same `runtimeType`, and returns whether dependents need to rebuild. ```dart class UnitsScope extends InheritedWidget { const UnitsScope({super.key, required this.units, required super.child}); final UnitSystem units; static UnitSystem of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UnitsScope>()!.units; @override bool updateShouldNotify(UnitsScope oldWidget) => units != oldWidget.units; } ``` ## What the framework does with it Each element keeps a map from widget `runtimeType` to the nearest **`InheritedElement`** of that type, inherited from its parent. So finding the nearest `UnitsScope` is a constant-time map lookup, not a walk up the tree. Because the map is keyed by the **exact** runtime type, a subclass of `UnitsScope` would not be found by `dependOnInheritedWidgetOfExactType<UnitsScope>()`. The lookup does two things: - it returns the widget, so the caller can read `units`; - it adds the calling element to the `InheritedElement`'s set of **dependents**. When the scope is rebuilt with a new instance and `updateShouldNotify` returns `true`, the `InheritedElement` calls `didChangeDependencies` on every dependent, which marks each one for rebuild in the next build phase. For a `StatefulWidget` dependent, `State.didChangeDependencies` runs before its next `build`. | Widget in the subtree | Rebuilt when units change? | |---|---| | Called `UnitsScope.of(context)` in build | Yes | | Did not call it, and its parent passed the same widget instance | No | | Did not call it, but its parent was itself rebuilt | Yes, through the ordinary parent rebuild | ## One toggle, step by step Suppose the user switches from metric to imperial on the settings screen: 1. The settings `State` above `UnitsScope` calls `setState` with `UnitSystem.imperial`. 2. In the next build phase that `State` rebuilds and returns a **new** `UnitsScope` instance carrying `imperial` and the same `child`. 3. The `InheritedElement` receives the new widget and calls `updateShouldNotify(oldWidget)`; `imperial != metric`, so it returns `true`. 4. Every registered dependent, such as each `TrailDistance` label, gets `didChangeDependencies` and is marked dirty. 5. The child subtree is compared as usual; because the `child` instance is unchanged, the intermediate list, cards and padding are skipped. 6. The dirty dependents rebuild in the same build phase, read `UnitsScope.of(context)` again and show miles. Nothing between the scope and the labels had to know that units exist, which is the whole point. ## Where the changing value comes from Widgets are immutable, so `UnitsScope` cannot change its own `units`. The usual shape is a `StatefulWidget` above it that holds the current value in its `State`, calls `setState` when the user toggles it, and returns `UnitsScope(units: _units, child: widget.child)` from `build`. Because `widget.child` is the same instance on each rebuild, the subtree between the scope and its dependents is skipped; only dependents rebuild. ## Common mistakes to avoid - Calling the accessor from a `BuildContext` that sits **above** the scope, for example in the same `build` method that creates it. The lookup only sees ancestors of that context, so it does not find a scope created below it. - Returning `true` unconditionally from `updateShouldNotify`, which rebuilds every dependent on every rebuild of the scope even when nothing changed. - Storing a **mutable** object and changing a field in place: no new widget instance is created, so `updateShouldNotify` is never called and nothing rebuilds. ## Where it sits among neighbours Packages such as Provider wrap this primitive with lifecycle management and are their own topic. Holding changing values in a `ChangeNotifier` or `ValueNotifier` is also its own topic. What stays here is the primitive itself: immutable data, a registering lookup and a notify rule.

  • Why is the lookup in an InheritedWidget's of method cheap even in a deep tree?
    Every element carries a map from widget runtime type to the nearest `InheritedElement` of that type, taken from its parent when it mounts. `dependOnInheritedWidgetOfExactType<T>()` is a map lookup, not an ancestor walk, so depth does not matter. The map is keyed by exact type, which is also why a subclass is not found by its parent type.
  • If UnitsScope holds a value that changes, which widget owns the changing state?
    Not `UnitsScope`: widgets are immutable. A `StatefulWidget` above it stores the current unit system in its `State`, calls `setState` on a toggle, and rebuilds `UnitsScope` with the new value and the same `child` instance. That new instance triggers `updateShouldNotify`, and only registered dependents rebuild.

A building's intercom list: residents who ask to be told about the water shutdown are put on the list, and when the notice changes only they get a call, not every flat in the building.

saying these in an interview costs you the question

  • InheritedWidget rebuilds its entire subtree whenever its data changes.
  • You change the value by mutating a field on the InheritedWidget.
  • Finding the nearest InheritedWidget walks up the tree on every call.
  • updateShouldNotify should just return true to be safe.
  • A subclass instance is found when you look up its parent type.
open as a page

In Flutter, why does calling MediaQuery.of(context) rebuild a widget more often than expected, and how do accessors like MediaQuery.sizeOf fix it?

level: middleimportance: should knowfreq 48%

basics

~20 s

MediaQuery.of registers an unconditional dependency, so the widget rebuilds whenever any MediaQueryData field changes, such as keyboard insets. MediaQuery is an InheritedModel; sizeOf, widthOf and paddingOf register one aspect and rebuild only when that value changes.

open as a page

In Flutter, when does the framework call an InheritedWidget's updateShouldNotify, and what goes wrong when it compares the wrong thing?

level: middleimportance: should knowfreq 42%

basics

~20 s

updateShouldNotify runs only when the InheritedWidget's position receives a new widget instance; it gets the old widget and returns whether dependents must rebuild. Comparing too loosely misses changes; comparing a freshly built object by identity rebuilds every dependent every time.

open as a page

In Flutter, how does InheritedNotifier differ from a plain InheritedWidget, and when would you use it for a changing setting?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

InheritedNotifier holds a Listenable and notifies its dependents whenever that Listenable fires, without anyone rebuilding the scope. Its updateShouldNotify only handles the notifier object being replaced. Use it when a shared, mutable controller should drive dependents directly.

open as a page

In Flutter, how do you build an InheritedModel so a widget that reads only the unit system does not rebuild when the map style changes?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Extend InheritedModel<Aspect>, expose accessors that call InheritedModel.inheritFrom with an aspect, and override updateShouldNotify plus updateShouldNotifyDependent, which receives each dependent's aspect set and returns true only if a field for one of those aspects changed.

open as a page