Flutter Lesson 38 of 83 3 min read
InheritedWidget in Flutter
Learn how InheritedWidget shares data down the widget tree in Flutter, how of(context) works, and why Provider is built on it.
On this page
You have used inherited widgets since your first app. Theme.of(context) and MediaQuery.sizeOf(context) both find data placed above in the tree, without it being passed through constructors. InheritedWidget is the mechanism behind them, and behind Provider.
What it does #
An InheritedWidget holds data. Any widget below it can ask for that data through context. When the data changes, exactly the widgets that asked are rebuilt.
Writing one #
import 'package:flutter/material.dart';
class AppConfig extends InheritedWidget {
const AppConfig({
super.key,
required this.apiUrl,
required this.currency,
required super.child,
});
final String apiUrl;
final String currency;
// The conventional accessor.
static AppConfig of(BuildContext context) {
final config = context.dependOnInheritedWidgetOfExactType<AppConfig>();
assert(config != null, 'No AppConfig found above this widget');
return config!;
}
@override
bool updateShouldNotify(AppConfig oldWidget) =>
apiUrl != oldWidget.apiUrl || currency != oldWidget.currency;
}
class PriceLabel extends StatelessWidget {
const PriceLabel(this.amount, {super.key});
final int amount;
@override
Widget build(BuildContext context) {
final currency = AppConfig.of(context).currency; // found from far above
return Text('$currency $amount', style: const TextStyle(fontSize: 28));
}
}
void main() {
runApp(
const AppConfig(
apiUrl: 'https://api.example.com',
currency: 'Rs.',
child: MaterialApp(
home: Scaffold(body: Center(child: PriceLabel(1499))),
),
),
);
}
| Piece | Purpose |
|---|---|
| Fields | The data being shared |
of(context) | Looks up the tree for the nearest AppConfig and registers the caller to be rebuilt when it changes |
updateShouldNotify | Decides whether dependants need rebuilding when the widget is replaced |
PriceLabel did not receive currency through its constructor, nor did any widget between it and the top.
Making the data changeable #
An InheritedWidget is immutable. To change what it holds, wrap it in a StatefulWidget that rebuilds it with new values.
import 'package:flutter/material.dart';
class CounterScope extends InheritedWidget {
const CounterScope({
super.key,
required this.count,
required this.increment,
required super.child,
});
final int count;
final VoidCallback increment;
static CounterScope of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<CounterScope>()!;
@override
bool updateShouldNotify(CounterScope oldWidget) => count != oldWidget.count;
}
class CounterHost extends StatefulWidget {
const CounterHost({super.key, required this.child});
final Widget child;
@override
State<CounterHost> createState() => _CounterHostState();
}
class _CounterHostState extends State<CounterHost> {
int _count = 0;
@override
Widget build(BuildContext context) {
return CounterScope(
count: _count,
increment: () => setState(() => _count++),
child: widget.child,
);
}
}
void main() {
runApp(
const CounterHost(
child: MaterialApp(home: CounterPage()),
),
);
}
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
final scope = CounterScope.of(context);
return Scaffold(
body: Center(child: Text('${scope.count}', style: const TextStyle(fontSize: 48))),
floatingActionButton: FloatingActionButton(
onPressed: scope.increment,
child: const Icon(Icons.add),
),
);
}
}
Because widget.child is the same object on every rebuild of CounterHost, Flutter does not rebuild the whole app. Only widgets that called CounterScope.of(context) are rebuilt.
Reading without listening #
dependOnInheritedWidgetOfExactType subscribes the caller to changes. To read once without subscribing, for example in a button’s onPressed, use:
final scope = context.getInheritedWidgetOfExactType<CounterScope>()!;
This is the difference between context.watch and context.read in Provider.
InheritedNotifier #
If your data is a ChangeNotifier or ValueNotifier, InheritedNotifier wires up the rebuilding for you, with no wrapping StatefulWidget.
class CartScope extends InheritedNotifier<CartModel> {
const CartScope({super.key, required CartModel cart, required super.child})
: super(notifier: cart);
static CartModel of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<CartScope>()!.notifier!;
}
Should you write these yourself? #
Rarely. It is a fair amount of code for each piece of state, which is exactly why the provider package exists: it is a friendlier wrapper around InheritedWidget.
It is still worth understanding, because:
Theme.of,MediaQuery.of,Navigator.ofandLocalizations.ofall work this way.- It explains the error “No X found in context”: you asked from a
contextthat is not below the provider. - It explains why
contextmatters, and why you cannot callof(context)ininitState.
Try it yourself #
Write a UserScope inherited widget that holds a user’s name and a function to change it. Read the name on two different screens, and change it from a third.