InheritedWidget
InheritedWidget is Flutter’s built-in way to share data down the widget tree without prop-drilling. It’s the foundation under Theme.of, MediaQuery.of, and most state-management libraries — understanding it makes the rest of Flutter make sense.
InheritedWidget, of(), Riverpod, scope
EXAMPLE
import 'package:flutter/material.dart';
// 1) The simplest InheritedWidget
class Counter extends InheritedWidget {
final int value;
final VoidCallback increment;
const Counter({
super.key,
required this.value,
required this.increment,
required super.child,
});
static Counter of(BuildContext context) {
final c = context.dependOnInheritedWidgetOfExactType<Counter>();
assert(c != null, 'No Counter in scope');
return c!;
}
@override
bool updateShouldNotify(Counter old) => old.value != value;
}
// 2) A stateful wrapper that owns + rebuilds the data
class CounterScope extends StatefulWidget {
final Widget child;
const CounterScope({super.key, required this.child});
@override
State<CounterScope> createState() => _CounterScopeState();
}
class _CounterScopeState extends State<CounterScope> {
int _value = 0;
void _increment() => setState(() => _value++);
@override
Widget build(BuildContext context) {
return Counter(value: _value, increment: _increment, child: widget.child);
}
}
// 3) Consumer — any descendant reads + rebuilds when value changes
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
final c = Counter.of(context);
return Text('Count: ${c.value}');
}
}
class IncrementButton extends StatelessWidget {
const IncrementButton({super.key});
@override
Widget build(BuildContext context) {
final c = Counter.of(context);
return ElevatedButton(onPressed: c.increment, child: const Text('+1'));
}
}
// Usage:
void main() {
runApp(
MaterialApp(
home: CounterScope(
child: Scaffold(
body: Center(child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [CounterDisplay(), IncrementButton()],
)),
),
),
),
);
}
// 4) dependOnInheritedWidgetOfExactType vs getElementForInheritedWidgetOfExactType
// • dependOn — subscribes; rebuilds the calling widget when InheritedWidget changes
// • getElement — reads WITHOUT subscribing (use in initState, callbacks, or when you don't need rebuild)
static Counter? maybeOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<Counter>();
}
static Counter ofUnsubscribed(BuildContext context) {
return context.getElementForInheritedWidgetOfExactType<Counter>()!.widget as Counter;
}
// 5) updateShouldNotify — control when descendants rebuild
// Return TRUE to rebuild subscribers; FALSE to skip.
// For typical data classes: compare by content (==) or by reference.
@override
bool updateShouldNotify(Counter old) => old.value != value;
// 6) InheritedNotifier — InheritedWidget + Listenable
class UserModel extends ChangeNotifier {
String? _name;
String? get name => _name;
void setName(String n) { _name = n; notifyListeners(); }
}
class UserScope extends InheritedNotifier<UserModel> {
const UserScope({super.key, required UserModel super.notifier, required super.child});
static UserModel of(BuildContext context) {
final scope = context.dependOnInheritedWidgetOfExactType<UserScope>()!;
return scope.notifier!;
}
}
// Now any consumer rebuilds when notifyListeners() is called.
// Wraps state-management much more conveniently than rolling your own update flag.
// 7) Examples Flutter uses internally
// Theme.of(context) → Theme is an InheritedWidget that wraps ThemeData
// MediaQuery.of(context) → MediaQuery is an InheritedWidget for screen info
// Localizations.of → InheritedWidget for translations
// Navigator.of(context) → walks the tree to find a NavigatorState
//
// Whenever you see X.of(context), look for X extends InheritedWidget under the hood.
// 8) Scoped InheritedWidgets — different values per subtree
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Counter(value: 0, increment: () {}, child: HomePage()),
builder: (context, child) => Counter(value: 100, increment: () {}, child: child!),
);
}
// Each subtree gets a different InheritedWidget instance.
// 9) Riverpod / Provider — built on InheritedWidget
// Provider: ChangeNotifierProvider, MultiProvider — InheritedWidgets under the hood
// Riverpod: Uses InheritedWidget for ProviderScope but adds compile-time safety
// BLoC: BlocProvider — also an InheritedWidget
//
// You rarely write InheritedWidget directly in apps using these libs. But knowing it
// means you can debug them and understand 'no [X] found in context' errors.
// 10) When to use InheritedWidget directly
// • Tight, focused state that you control end-to-end
// • Library code where you don't want a third-party dependency
// • Examples / teaching / tests
//
// When to use a state library:
// • Multi-page apps with cross-cutting state
// • Async state (network) — Riverpod's AsyncValue handles loading/error/data
// • Code generation, dev tools, testing affordances
// 11) InheritedWidget + Builder — limit rebuild scope
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const ExpensiveHeader(), // doesn't depend on counter
Builder(
builder: (ctx) { // dependency LIMITED to this subtree
final c = Counter.of(ctx);
return Text('${c.value}');
},
),
],
);
}
}
// 12) Common bugs
// • Calling X.of(context) in initState — context dependencies not registered there; do it in didChangeDependencies
// • updateShouldNotify returning true always — every change rebuilds everything; defeat caching
// • InheritedWidget without a stateful owner — value never changes
// • Mutating the InheritedWidget's data directly — Flutter doesn't notice; rebuild via setState OR notifyListeners
// • Two InheritedWidget instances with the same data → all subscribers rebuild on any swap
// • 'No XxxScope found in context' — widget tree above doesn't have a provider; check Navigator/route boundaries
// • Storing controllers (TextEditingController) in InheritedWidget — they're disposable; manage lifetimes carefully
// • Use after dispose — calling X.of(context) after navigation pop is fine until the widget unmounts
Why it matters
InheritedWidget is how Flutter pipes data down the tree without prop-drilling. Write a stateful owner that returns the InheritedWidget, expose of(context), and override updateShouldNotify to control when descendants rebuild. Most state libraries (Provider, Riverpod, BLoC) build directly on this; understanding it explains every X.of(context) in Flutter.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
class CountModel extends InheritedWidget {
final int value;
const CountModel({super.key, required this.value, required super.child});
static CountModel? of(BuildContext c) => c.dependOnInheritedWidgetOfExactType<CountModel>();
@override
bool updateShouldNotify(CountModel old) => old.value != value;
}
Try it Yourself »
Discussion
Loading…