InheritedWidget Internals
4 min read
State Management
InheritedWidget is Flutter's built-in dependency-propagation primitive — the foundation that Provider, Theme, MediaQuery, and Navigator are all built on. It works in two steps: descendants register via dependOnInheritedWidgetOfExactType<T>(), and when updateShouldNotify returns true, only those registered dependents rebuild.
1. Registration — dependOnInheritedWidgetOfExactType<T>(context) looks up the nearest ancestor of type T in an O(1) hash on the Element (_inheritedElements map), and stores the calling Element in that InheritedElement's dependent list.
2. Notification — when a new InheritedWidget is placed at the same spot, Flutter calls updateShouldNotify(oldWidget). If true, Flutter walks the dependent list and calls didChangeDependencies() on each, marking them dirty for the next frame.
| Step | Where it lives | Cost |
|---|---|---|
| Lookup an InheritedWidget | Element._inheritedElements[T] | O(1) |
| Register a dependent | Add Element to dependents list | O(1) |
| Notify on change | Walk dependents list | O(D) where D = dependent count |
The framework can do this efficiently because each Element keeps an inherited-widget cache updated as it's mounted into the tree.
Code in action — a mini Provider clone
class AppState extends InheritedWidget {
const AppState({
super.key,
required this.counter,
required this.increment,
required super.child,
});
final int counter;
final VoidCallback increment;
@override
bool updateShouldNotify(AppState old) => counter != old.counter;
// Subscribing access (triggers rebuilds)
static AppState of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<AppState>()!;
// One-shot read (no dependency registered)
static AppState read(BuildContext context) =>
context.getInheritedWidgetOfExactType<AppState>()!;
}
// Stateful wrapper that creates a new InheritedWidget when state changes
class AppStateProvider extends StatefulWidget {
const AppStateProvider({super.key, required this.child});
final Widget child;
@override
State<AppStateProvider> createState() => _AppStateProviderState();
}
class _AppStateProviderState extends State<AppStateProvider> {
int _count = 0;
@override
Widget build(BuildContext context) => AppState(
counter: _count,
increment: () => setState(() => _count++),
child: widget.child,
);
}
// Consumer
class CounterText extends StatelessWidget {
@override
Widget build(BuildContext context) =>
Text('${AppState.of(context).counter}'); // registers dependency
}
That's all Provider is, with extra ergonomics on top.
dependOn… vs getInheritedWidgetOfExactType
| Method | Registers a dependency? | Use in |
|---|---|---|
context.dependOnInheritedWidgetOfExactType<T>() | ✅ Yes — rebuilds on change | build, didChangeDependencies |
context.getInheritedWidgetOfExactType<T>() | ❌ No — one-time read | initState (when you don't want rebuilds), callbacks |
Common mistakes to avoid
// ❌ updateShouldNotify => true always — every parent rebuild rebuilds all dependents
@override
bool updateShouldNotify(_) => true;
// ✅ Compare meaningfully
// ❌ Reading from InheritedWidget in initState with dependOn... — partially-wired context
@override
void initState() {
super.initState();
final t = Theme.of(context); // might miss future updates
}
// ✅ Read in didChangeDependencies, OR use getInheritedWidgetOfExactType for a one-shot read
// ❌ Storing mutable state directly in the InheritedWidget
class Cart extends InheritedWidget { List<Item> items = []; ... }
// items.add(...) mutates in place → updateShouldNotify can't detect it
// ✅ Wrap mutable state in a StatefulWidget above the InheritedWidget,
// and rebuild the InheritedWidget with new field values
// ❌ Multiple InheritedWidgets of the same type stacked
// .of(context) finds the NEAREST — subtle bugs if you didn't intend nesting
// ❌ Forgetting that the same .of() call in different subtrees may yield different widgets
// That's a feature (scoped overrides) — but easy to forget
Interview follow-ups
-
What's the time complexity of finding an InheritedWidget? O(1). Each Element keeps an
_inheritedElementsmap keyed by runtime type, populated as the Element is mounted.dependOnInheritedWidgetOfExactType<T>()is a single map lookup — not a tree walk. -
How does Flutter know which widgets depend on an InheritedWidget? Each
InheritedElementmaintains a_dependentsset. When a descendant callsdependOnInheritedWidgetOfExactType<T>, the framework adds that descendant's Element toT's_dependents. OnupdateShouldNotifyreturning true, Flutter walks that set and marks each dependent dirty. -
Why is
updateShouldNotifyseparate from constructor equality? Because two InheritedWidget instances with equal fields are still different objects — the default==check wouldn't help.updateShouldNotifylets you opt into custom equality semantics for change detection, independent of the widget's identity. -
What's the relationship between
InheritedWidget,InheritedNotifier, andInheritedModel?InheritedNotifier<T extends Listenable>is a built-in wrapper that rebuilds dependents whenTnotifies — useful forChangeNotifier/ValueNotifier.InheritedModel<T>is a finer-grained version where dependents subscribe to aspects of the data, rebuilding only when their declared aspect changes — same idea as Provider'sselect, just at framework level.
How helpful was this content?
Please sign in to rate this article.