InheritedWidget is a Flutter widget class that efficiently propagates data down the widget tree, allowing descendants to access it without passing the data explicitly through every constructor.
When an InheritedWidget is updated, Flutter uses updateShouldNotify to determine whether widgets that depend on it should be notified and potentially rebuilt. An unrelated ancestor rebuild does not, by itself, rebuild every dependent widget. However, a regular InheritedWidget tracks dependencies at the widget level rather than per individual field, so when it reports a change, all widgets depending on that InheritedWidget may rebuild.
This mechanism is used by Flutter APIs such as Theme and MediaQuery, and by higher-level packages such as provider. InheritedWidget is therefore a low-level Flutter mechanism for propagating values and tracking dependencies, rather than a competing alternative to those APIs.
InheritedWidget is part of Flutter's core widgets library and requires no external package.context.dependOnInheritedWidgetOfExactType<T>(), typically exposed through a static of(context) method. This also registers the widget as a dependent.InheritedWidget is updated, updateShouldNotify determines whether its dependents should be notified that the inherited data has changed and potentially rebuilt.InheritedWidget does not track dependencies at the individual-field level: when updateShouldNotify reports a change, all widgets depending on that InheritedWidget may rebuild.Theme.of(context) and MediaQuery.of(context) use the InheritedWidget mechanism under the hood to propagate values and track dependencies.provider package builds a higher-level API around this mechanism, reducing boilerplate and making dependency access and lifecycle management more convenient. InheritedWidget is therefore an underlying Flutter mechanism, not a competing alternative to provider.Define a class extending InheritedWidget, override updateShouldNotify, and add a static of method for descendants to call.
import 'package:flutter/material.dart';
class CartInfo extends InheritedWidget {
const CartInfo({super.key, required this.itemCount, required super.child});
final int itemCount;
static CartInfo of(BuildContext context) {
final CartInfo? result =
context.dependOnInheritedWidgetOfExactType<CartInfo>();
assert(result != null, 'No CartInfo found in context');
return result!;
}
@override
bool updateShouldNotify(CartInfo oldWidget) {
return itemCount != oldWidget.itemCount;
}
}Any descendant can now call CartInfo.of(context).itemCount and will be notified when itemCount changes, rather than merely because an unrelated ancestor widget rebuilds.
| Raw InheritedWidget | provider package |
|---|---|
Boilerplate | |
| You implement the inherited widget, typically including an of method and updateShouldNotify | Ready-made providers and BuildContext extensions such as watch, read, and select |
State changes | |
| You define when dependents should be notified through updateShouldNotify | Providers can listen to objects such as ChangeNotifier and propagate their notifications |
Multiple values | |
| You can compose multiple InheritedWidgets, but the structure and access patterns are yours to design | MultiProvider makes composing multiple providers more convenient |
Selective dependencies | |
| A regular InheritedWidget does not track dependencies per field; use separate inherited widgets or InheritedModel for finer-grained dependencies | context.select can listen to a selected part of a provided value |
Best for | |
| Understanding Flutter's underlying mechanism and implementing framework/library-level abstractions | Everyday dependency injection and state management in application code |
Verdict: provider is the practical choice for day-to-day application state, while understanding InheritedWidget gives you the foundation for understanding how Flutter propagates inherited data — and how APIs such as Theme, MediaQuery, and provider build on that mechanism.
updateShouldNotify precisely – return true only when the inherited data has changed in a way that should cause its dependents to rebuild.of(context) method when appropriate – it gives descendants a familiar way to access the inherited data and register a dependency, following the same pattern used by APIs such as Theme.of(context) and MediaQuery.of(context).dependOnInheritedWidgetOfExactType when a widget should react to changes – unlike getInheritedWidgetOfExactType, it registers the widget as a dependent.provider for typical application state and dependency injection – writing custom inherited widgets for everyday app code adds boilerplate that higher-level APIs can handle for you.MediaQuery.sizeOf(context) when you only need the screen size instead of depending on the entire MediaQueryData.getInheritedWidgetOfExactType instead of dependOnInheritedWidgetOfExactType. The former does not register a dependency, so the widget will not be notified when the inherited widget changes.InheritedWidget and provider as competing choices. provider builds on Flutter's inherited-widget mechanism; the real question is whether to write the boilerplate yourself or let the package handle it.updateShouldNotify too broadly or too narrowly. Returning true for every update can cause unnecessary rebuilds, while returning false when relevant data has changed prevents dependents from updating.InheritedWidget. Inherited widgets are immutable, like all Flutter widgets. If the inherited data needs mutable state, expose a separate mutable object such as a ChangeNotifier and let an appropriate higher-level abstraction manage its notifications.initState. If a widget needs to react to changes, establish the dependency from build or didChangeDependencies rather than initState.
Flutter is known for its seamless UI/UX features and robust design elements. It allowed us to deliver a unique customer experience in the “CA24 Mobile” banking app. This article shares some of the learnings and pain points behind implementing it in this large project.