InheritedWidget is a special widget in Flutter that passes data down the widget tree without explicitly passing it through constructors. Child widgets access data through BuildContext and automatically subscribe to updates. When data in InheritedWidget changes, all dependent widgets rebuild. According to Flutter API Reference, 2025, InheritedWidget is the foundation of Theme, MediaQuery, Localizations, and most state management libraries.
Key Takeaways
InheritedWidget is a widget that makes its data available to all descendants in the Widget Tree. Unlike a regular widget that only passes data through constructors to child elements, InheritedWidget allows any widget in the subtree to access data without a chain of parameters. This solves the “prop drilling” problem — passing data through many intermediate widgets that don’t use this data themselves.
Flutter includes several built-in InheritedWidgets: Theme (color scheme and styles), MediaQuery (screen size, orientation, pixel density), Localizations (localized strings), Directionality (text direction), DefaultTextStyle (default text style). These widgets are set by root widgets like MaterialApp and are available throughout the application.
InheritedWidget does not have its own state — it stores data passed through the constructor. When the parent of InheritedWidget rebuilds with new data, the updateShouldNotify method is called to compare old and new data. If the method returns true, all dependent widgets are marked for rebuilding. This is a simple but effective reactive update mechanism.
The data passing mechanism through InheritedWidget is based on the Element Tree. When a widget calls dependOnInheritedWidgetOfExactType, the corresponding element registers a dependency on InheritedElement. When InheritedWidget changes, InheritedElement notifies all dependent elements, which rebuild in the next frame.
The dependOnInheritedWidgetOfExactType method doesn’t just find InheritedWidget in the tree — it subscribes the current element to notifications. If you used findAncestorWidgetOfExactType instead of dependOn, the widget would get the data but wouldn’t rebuild when it changes. This is an important difference: dependOn is a subscription, findAncestor is a one-time lookup.
When a widget requests an InheritedWidget, Flutter walks up the Element Tree from the current element to the root, checking each InheritedElement for a type match. The first matching InheritedElement is returned. This means the nearest InheritedWidget in the tree takes priority — you can override data at a specific level by placing InheritedWidget closer to the descendants.
class ThemeData {
final Color primaryColor;
final TextTheme textTheme;
const ThemeData({required this.primaryColor, required this.textTheme});
}
class MyTheme extends InheritedWidget {
final ThemeData data;
const MyTheme({required this.data, required Widget child}) : super(child: child);
static MyTheme of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
assert(widget != null, "MyTheme not found in tree");
return widget!;
}
@override
bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}
In this example, MyTheme uses a static of method to provide data to descendants. The dependOnInheritedWidgetOfExactType method registers a dependency, and updateShouldNotify compares old and new data to determine whether dependent widgets need to rebuild.
Creating a custom InheritedWidget consists of two steps: defining a class that extends InheritedWidget, and implementing a static of method for access from descendants. Data is passed through the constructor, and the updateShouldNotify method determines when dependent widgets should rebuild.
The class must extend InheritedWidget and accept data through a constructor with a required child parameter. Data can be of any type: primitives, objects, functions. The main rule is that data must be immutable so that old and new values can be reliably compared.
The static of method takes BuildContext and returns InheritedWidget data. Inside, it calls dependOnInheritedWidgetOfExactType, which finds the nearest InheritedWidget of the specified type in the tree. If InheritedWidget is not found, the method throws an exception or returns a default value depending on the implementation.
To access data, the widget calls MyWidget.of(context) inside the build method. Flutter automatically subscribes the widget to updates. If data changes, the widget rebuilds in the next frame. This allows for clean and declarative code without unnecessary parameters.
class UserPreferences extends InheritedWidget {
final String languageCode;
final bool darkMode;
const UserPreferences({
required this.languageCode,
required this.darkMode,
required Widget child,
}) : super(child: child);
static UserPreferences of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
}
@override
bool updateShouldNotify(UserPreferences oldWidget) =>
oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}
In this example, UserPreferences stores user settings. The updateShouldNotify method compares each field individually, which prevents unnecessary rebuilds when only one parameter changes. Use a similar approach for your own InheritedWidgets with multiple fields.
updateShouldNotify is the key method of InheritedWidget that determines whether dependent widgets need to be notified about data changes. If the method returns false, dependent widgets do not rebuild, even if the InheritedWidget itself received a new instance with the same data. This is critically important for performance.
Compare only those fields that have actually changed and affect the display. If InheritedWidget contains 10 fields but only one affects the UI, check only that field. For collections, use deep comparison or immutable data structures. Do not use == for List or Map, as they compare by reference.
The most common mistake is returning true without comparison. This causes all dependent widgets to rebuild on every parent update, even if data hasn’t changed. The second mistake is returning false when data has changed, leading to a stale UI. The third is complex comparison that runs every frame and slows things down.
InheritedWidget and callbacks (passing functions through constructors) solve different problems. InheritedWidget is suitable for data needed by many widgets at different levels of the tree. Callbacks are convenient for one-way event passing from parent to a specific child or vice versa. The choice depends on application architecture and update frequency.
Use InheritedWidget when data is needed by many widgets at different nesting levels: app theme, user settings, device information, current session data. InheritedWidget is especially effective for “global” data that rarely changes but is needed in different parts of the UI.
Callbacks (callback functions) are suitable for passing events from a child widget to a parent: button press, list item selection, form submission. Callbacks explicitly indicate what actions a child can perform and don’t create hidden dependencies. For passing data down the tree over a small number of levels, it’s also simpler to use constructor parameters.
| Criterion | InheritedWidget | Callbacks |
|---|---|---|
| Direction | Top-down (parent → descendants) | Bottom-up (child → parent) or direct |
| Scope | Entire subtree | Specific widget |
| Rebuilding | Automatic on data change | Requires manual setState |
| Complexity | Medium (requires InheritedWidget class) | Low (just a function) |
Provider and Riverpod are popular state management libraries in Flutter built on top of InheritedWidget. They extend its capabilities: add ChangeNotifier support, automatic disposal on unmount, lazy initialization, and simplified syntax with generics.
Provider uses InheritedWidget to pass an object of any type down the tree. ChangeNotifierProvider tracks changes through ChangeNotifier and calls updateShouldNotify when notifyListeners is called. This frees the developer from manually creating InheritedWidget and implementing updateShouldNotify.
Direct InheritedWidget gives more control and doesn’t require external dependencies. Provider provides ready infrastructure: Consumer, Selector, MultiProvider, ProxyProvider. The choice depends on application complexity. For simple projects, direct InheritedWidget is sufficient; for large ones, Provider or Riverpod reduce boilerplate code.
// Direct InheritedWidget
class UserProvider extends InheritedWidget {
final UserData userData;
const UserProvider({required this.userData, required Widget child}) : super(child: child);
static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
@override
bool updateShouldNotify(UserProvider old) => old.userData != userData;
}
// Provider equivalent
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Both approaches in the example solve the same problem — passing UserData down the tree. Provider reduces code volume but hides the InheritedWidget mechanics. Direct InheritedWidget gives full control and understanding of what’s happening, which is especially important when learning Flutter and debugging complex rebuild issues.
Frequently Asked Questions
InheritedWidget makes data available to all descendants through BuildContext, while a regular widget only passes data through the constructor. InheritedWidget also subscribes descendants to data updates.
Dependent widgets only rebuild when updateShouldNotify returns true. If the method is implemented correctly, rebuilding only happens when data actually changes, not on every parent rebuild.
Yes, you can use any number of InheritedWidgets in the same tree. Each provides data of a specific type, and widgets can get data from multiple InheritedWidgets simultaneously.
dependOn subscribes the widget to updates — when data changes, the widget rebuilds. findAncestor performs a one-time lookup without subscription, and the widget won’t know about data changes.
For simple state (theme, settings) InheritedWidget is sufficient. For complex state with business logic, use Provider, Riverpod, or BLoC — they are built on InheritedWidget and add the necessary infrastructure.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also