InheritedWidget
The problem it solves
Section titled “The problem it solves”Passing data down a deep tree by hand — through every constructor — is miserable. This is “prop drilling,” and it couples every intermediate widget to data it does not use.
InheritedWidget is Flutter’s built-in answer. You place one high in the tree, and any descendant can read it directly via context, no matter how deep, without a single widget in between knowing about it. Theme, MediaQuery, Navigator, and Provider are all InheritedWidgets — you have been using it all along.
How a descendant reads it
Section titled “How a descendant reads it”A descendant calls dependOnInheritedWidgetOfExactType, usually wrapped in a static of(context) method by convention:
class ThemeConfig extends InheritedWidget { const ThemeConfig({super.key, required this.color, required super.child}); final Color color;
// Convention: a static `of` that registers the caller as a dependent. static ThemeConfig of(BuildContext context) { final result = context.dependOnInheritedWidgetOfExactType<ThemeConfig>(); assert(result != null, 'No ThemeConfig found in context'); return result!; }
// Called when a new ThemeConfig replaces the old one: should dependents rebuild? @override bool updateShouldNotify(ThemeConfig oldWidget) => color != oldWidget.color;}
// Anywhere below it:final color = ThemeConfig.of(context).color;Two mechanics matter here. dependOnInheritedWidgetOfExactType does two things at once: it finds the nearest ancestor of that type (a fast, cached lookup up the element tree), and it registers the calling element as a dependent. And updateShouldNotify decides, when the inherited widget is rebuilt with new data, whether dependents actually need to rebuild.
Only dependents rebuild
Section titled “Only dependents rebuild”This is the payoff. When the InheritedWidget updates and updateShouldNotify returns true, Flutter rebuilds only the widgets that called of(context) — not the whole subtree. Widgets in between that never depended on it are untouched.
flowchart TB iw["InheritedWidget (color changes)"] --> a["Widget A (did not depend) NOT rebuilt"] iw --> b["Widget B called of(context) REBUILT"] iw --> c["Widget C (did not depend) NOT rebuilt"]
That targeted rebuild is why InheritedWidget scales: reading is a cheap upward lookup, and updates only ripple to true dependents. Every higher-level state solution is, underneath, this mechanism plus ergonomics.