InheritedWidget
InheritedWidget is a special low-level base class in Flutter. It is designed to propagate data efficiently from a parent widget down to any of its descendants in the widget tree, without having to manually pass variables through constructors (a problem known as Prop Drilling).
Advanced packages like Provider and scoped_model are built entirely on top of InheritedWidget.
1. The Prop Drilling Problem
Imagine you have a ThemeSettings object at the root of your application, and a small ThemeToggle button 5 levels deep in the widget tree.
Without InheritedWidget, you would have to pass themeData through the constructor of HomeScreen, DashboardBody, HeaderWidget, ProfileCard, and finally ThemeToggle. This is boilerplate-heavy, error-prone, and hard to maintain.
With InheritedWidget, the ThemeToggle can fetch the settings directly in $O(1)$ time complexity:
final settings = ThemeProvider.of(context);
2. Complete Code Example
Here is a complete, working implementation of a theme config sharing mechanism using a custom InheritedWidget and a stateful wrapper.
Step 1: Create the InheritedWidget
import 'package:flutter/material.dart';
class ThemeProvider extends InheritedWidget {
final String themeName;
final VoidCallback toggleTheme;
const ThemeProvider({
super.key,
required this.themeName,
required this.toggleTheme,
required super.child,
});
// Convenice helper to lookup the widget from the build context
static ThemeProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
}
// Determine if dependent widgets should rebuild when properties change
bool updateShouldNotify(ThemeProvider oldWidget) {
return oldWidget.themeName != themeName;
}
}
Step 2: Create a Stateful Wrapper
An InheritedWidget is immutable. To update its data and trigger rebuilds, wrap it in a StatefulWidget:
class ThemeStateContainer extends StatefulWidget {
final Widget child;
const ThemeStateContainer({super.key, required this.child});
State<ThemeStateContainer> createState() => _ThemeStateContainerState();
}
class _ThemeStateContainerState extends State<ThemeStateContainer> {
String _themeName = 'Light';
void _toggleTheme() {
setState(() {
_themeName = _themeName == 'Light' ? 'Dark' : 'Light';
});
}
Widget build(BuildContext context) {
return ThemeProvider(
themeName: _themeName,
toggleTheme: _toggleTheme,
child: widget.child,
);
}
}
Step 3: Consume in Descendant Widgets
Now, place ThemeStateContainer at the root, and any widget underneath can consume and trigger changes:
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const ThemeStateContainer(
child: MaterialApp(
home: HomeScreen(),
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
Widget build(BuildContext context) {
// 1. Obtain the nearest InheritedWidget instance
final themeProvider = ThemeProvider.of(context);
final theme = themeProvider?.themeName ?? 'Default';
return Scaffold(
appBar: AppBar(title: Text('Theme: $theme')),
body: Center(
child: ElevatedButton(
onPressed: themeProvider?.toggleTheme,
child: const Text('Toggle Theme'),
),
),
);
}
}
3. How the Lookup Works Under the Hood
When you call context.dependOnInheritedWidgetOfExactType<T>():
- Flutter looks up a pre-cached hash map of inherited widgets stored in the current element's ancestor tree (which makes lookup an instant $O(1)$ operation).
- It registers the current widget's
Elementas a dependent of the targetInheritedWidget. - When
updateShouldNotifyreturnstrueduring a rebuild, Flutter automatically schedules a rebuild for all registered dependent elements.
4. Advanced Inherited Utilities
InheritedNotifier
If your data updates frequently (e.g. from a Listenable or ChangeNotifier), subclassing InheritedNotifier simplifies updates. It automatically calls rebuilds on dependents whenever the notifier emits a change, without manually calling setState in a wrapper widget.
InheritedModel
By default, when an InheritedWidget triggers a rebuild, every dependent widget rebuilds. If a widget only cares about part of the data (e.g., only the profile name, not the shopping cart size), you can use InheritedModel. It allows dependents to select a specific "aspect" to listen to, preventing unnecessary rebuilds.