InheritedWidget Internals
4 min read
State Management
InheritedWidget is Flutter's built-in way to share data down the widget tree. The foundation that Provider, Theme, MediaQuery and Navigator are all built upon.
It works in two steps: widgets subscribe using dependOnInheritedWidgetOfExactType<T>(), and later when updateShouldNotify returns true, only those subscribed widget gets rebuild.
1. Register — when a widget calls dependOnInheritedWidgetOfExactType<T>(), Flutter finds the nearest InheritedWidget and registers that widget as a dependent.
2. Notify — when the InheritedWidget updates, Flutter calls updateShouldNotify(). If it returns true, only the registered dependents are rebuilt.
| Step | Where it lives |
|---|---|
| Lookup an InheritedWidget | Element._inheritedElements[T] |
| Register a dependent | Add Element to dependents list |
| Notify on change | Walk dependents list |
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 CounterProvider extends InheritedWidget {
final int counter;
const CounterProvider({
super.key,
required super.child,
required this.counter,
});
@override
bool updateShouldNotify(CounterProvider oldWidget) {
return counter != oldWidget.counter;
}
/// Registers a dependency.
static CounterProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<CounterProvider>();
}
}
/// Consumer
class CounterText extends StatelessWidget {
const CounterText({super.key});
@override
Widget build(BuildContext context) {
final count = CounterProvider.of(context).count; // registers dependency
print('CounterText rebuilt');
return Text(
'Count: $count',
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
);
}
}That's all Provider is, with extra ergonomics on top.
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 forgetInterview 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.
How helpful was this content?
Please sign in to rate this article.