skip to content

In Flutter, what is the difference between dependOnInheritedWidgetOfExactType and getInheritedWidgetOfExactType, and when would you choose each?

level: middleimportance: should knowfreq 38%

answer

  1. both find the nearest of type T
  2. only one subscribes
  3. rebuild when that widget changes
  4. a one-off read in a handler
  5. both O(1); subscribing costs rebuilds

basics

~20 s

Both return the nearest InheritedWidget of type T in O(1); dependOnInheritedWidgetOfExactType also registers the context as a dependent, so it rebuilds when that widget changes. Use it for data build displays; use getInheritedWidgetOfExactType for one-off reads where a rebuild is unwanted.

solid answer

~40 s

`dependOnInheritedWidgetOfExactType<T>()` returns the nearest `T` above the context and registers the context with it; when that inherited widget changes, is replaced or disappears, the context is rebuilt and dependent States get `didChangeDependencies`. It is what `Theme.of` and most `X.of` helpers call, and it belongs in `build`, `didChangeDependencies`, or layout and paint callbacks. `getInheritedWidgetOfExactType<T>()` does the same O(1) lookup without the dependency, so the context is never rebuilt because of it; the framework describes it as meant for uncommon cases where a dependency is undesirable, such as reading a service once in a tap handler. The rule of thumb: if `build`'s output depends on the value, subscribe; if you only need the value at one moment and must not cause rebuilds, use the non-subscribing lookup. Neither should be called from `dispose`.

code

dart · 33 lines
dart
import 'package:flutter/material.dart';

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

  final int maxRenewals;

  @override
  bool updateShouldNotify(LoanPolicy oldWidget) => maxRenewals != oldWidget.maxRenewals;
}

class RenewButton extends StatelessWidget {
  const RenewButton({super.key, required this.renewals, required this.onRenew});

  final int renewals;
  final VoidCallback onRenew;

  @override
  Widget build(BuildContext context) {
    // Output depends on the policy: subscribe, so a new limit rebuilds the button.
    final policy = context.dependOnInheritedWidgetOfExactType<LoanPolicy>()!;
    return FilledButton(
      onPressed: renewals < policy.maxRenewals ? onRenew : null,
      child: Text('Renew ($renewals/${policy.maxRenewals})'),
    );
  }
}

void logPolicy(BuildContext context) {
  // One-off read in a handler: no dependency, no extra rebuilds.
  final policy = context.getInheritedWidgetOfExactType<LoanPolicy>();
  debugPrint('max renewals: ${policy?.maxRenewals}');
}

go deeper

for a junior

Recall that Theme.of and similar helpers subscribe your widget to changes, so it rebuilds when the theme changes.

for a middle

Explain that both lookups are O(1) and differ only in registering a dependency, and pick the right one for build versus a one-off read.

for a senior

Catch stale-UI bugs caused by non-subscribing reads in build, and justify non-subscribing reads where rebuild edges would be wasteful in hot paths.

for a principal

Shape shared scopes so services read once and data that drives UI are separated, keeping rebuild dependencies intentional across a large widget tree.

## Two lookups, one difference An **InheritedWidget** makes data available to its whole subtree. Any descendant can find the nearest one of a given type through its `BuildContext`. The framework offers two methods for that, and they differ in exactly one thing: whether the lookup **subscribes** the caller. | | `dependOnInheritedWidgetOfExactType<T>()` | `getInheritedWidgetOfExactType<T>()` | |---|---|---| | Returns | Nearest `T` above the context, or `null` | Nearest `T` above the context, or `null` | | Cost | O(1) with a small constant | O(1) with a small constant | | Registers a dependency | Yes | No | | Context rebuilt when `T` changes | Yes | No | | Typical caller | `build`, `didChangeDependencies`, `X.of` helpers | One-off reads where a rebuild is unwanted | Both are constant time because each element keeps a map from inherited-widget type to the nearest such element above it. The cost of the subscribing variant is not the lookup; it is that the widget will be rebuilt more often, which the documentation states directly. ## When to subscribe Subscribe whenever the widget's output depends on the value: - a loan tile styled from the theme; - a due-date label formatted for the current locale; - a renew button disabled when a `LoanPolicy` inherited widget says the limit is reached. If you read such a value without subscribing, the widget shows the old value until something unrelated rebuilds it: a bug the framework cannot detect for you. That is why the `X.of` convention, such as `Theme.of` or `MediaQuery.of`, uses the subscribing variant, and why most code never calls either method directly. ## When not to subscribe The framework calls the non-subscribing variant 'meant for those uncommon use cases where a dependency is undesirable'. Examples: 1. A tap handler on the loan screen reads a `LibraryServices` inherited widget once to get the API client for a renewal call. The screen's output does not depend on which client instance it is, so a rebuild when the scope changes would be wasted. 2. Framework-level or library code that needs a value during an operation but must not create rebuild edges for every widget it touches. The documentation also allows calling the **subscribing** variant from event handlers or timers to obtain a value once, as long as it is not cached; the only side effect is the extra dependency. So using `Theme.of` in an `onPressed` is not a bug; the non-subscribing variant is a refinement, not a requirement. ## Where neither belongs - **Constructors and `initState`**: the subscribing variant asserts there; lifecycle rules are covered with the State callbacks. - **`dispose`**: the element tree is no longer stable, and the documentation says to save a reference in `didChangeDependencies` instead. - **Caches that outlive a single synchronous function**: the value can change after you read it. ## Related lookups - `getElementForInheritedWidgetOfExactType<T>()` returns the element instead of the widget, also without a dependency. - `dependOnInheritedElement(element, aspect: ...)` subscribes to a specific element, optionally to one aspect, which is how `InheritedModel` narrows rebuilds; aspects belong to the inherited-data topic. - `findAncestorWidgetOfExactType<T>()` finds any widget type, not just inherited ones, but walks the tree in O(N) and never subscribes.

  • Why do most static X.of methods use the subscribing lookup?
    Because they are designed to be called from `build` to render ambient data such as a theme, media size or locale, and a widget that renders such data must rebuild when it changes. Subscribing makes that automatic. Many also offer a `maybeOf` variant that returns `null` instead of throwing when no ancestor exists.
  • Is calling Theme.of(context) inside a button's onPressed wrong?
    No. The framework documents that the subscribing lookup may be called from event handlers or timers to obtain a value once, provided the value is not cached and reused later. The only side effect is that the widget now depends on the theme and rebuilds when it changes, which rarely matters for a widget that usually reads the theme anyway.

saying these in an interview costs you the question

  • getInheritedWidgetOfExactType is O(N) while dependOn is O(1)
  • Both methods make the widget rebuild when the value changes
  • Always prefer the non-subscribing lookup to avoid rebuilds, even in build
  • dependOnInheritedWidgetOfExactType may only be called inside build