This tutorial explains what is InheritedWidget
in Flutter and how to use it.
InheritedWidget
is a base class that allows classes that extend it to propagate information down the tree efficiently. Basically, it works by notifying registered build contexts if there is any change. Therefore, the descendant widgets that depend on it will only be rebuilt if necessary. In addition, the descendant widgets can also get the nearest instance of the inherited widget and access non-private properties.
Using InheritedWidget
Because InheritedWidget
is an abstract class, you need to create a class that extends it.
Extending InheritedWidget
requires you to override updateShouldNotify
method. The method is used to determine whether the widgets that inherit from this widget should be notified. It returns boolean and accepts a parameter whose type is the same as the class type.
bool updateShouldNotify(covariant InheritedWidget oldWidget);
When the widget is rebuilt, the method will be invoked with the previous instance of the widget passed as the parameter. Therefore, you can compare the properties of the old instance with the current properties to determine whether the descendant widgets that inherit from this widget should be rebuilt too.
There is an important method of BuildContext
you need to know.
T dependOnInheritedWidgetOfExactType<T> extends InheritedWidget>({ Object aspect });
The method is used to get the nearest widget of the given type which must be a concrete subclass of InheritedWidget
. Therefore, the descendant widgets can access the non-private properties of the inherited widget. In addition, it also registers the build context with that widget. If the widget changes (including introduction or removal of a widget with that type), the registered build contexts will be notified if updateShouldNotify
returns true
.
A common convention is to create a method named of
in the class that extends InheritedWidget
. It accepts a parameter of type BuildContext
and calls the dependOnInheritedWidgetOfExactType
method to get an instance of the nearest widget whose type is Info
.
Below is a class named Info
which extends InheritedWidget
. It overrides the updateShouldNotify
and has a static method of
which is used to get the nearest widget of type Info
from the given BuildContext
.
class Info extends InheritedWidget {
const Info({
Key key,
@required this.score,
@required Widget child,
}) : assert(score != null),
assert(child != null),
super(key: key, child: child);
final int score;
static Info of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<Info>();
}
@override
bool updateShouldNotify(covariant Info oldWidget) {
return score != oldWidget.score;
}
}
Below is another widget class named CurrentScore
. It uses the score
property of Info
to be displayed as text. CurrentScore
is supposed to be put below an Info
widget in the tree.
class CurrentScore extends StatelessWidget {
const CurrentScore();
@override
Widget build(BuildContext context) {
final Info info = Info.of(context);
return Container(
child: Text(info?.score.toString()),
);
}
}
The code below is a state class which puts the CurrentScore
widget under Info
widget in the tree. The score
property of Info
depends on a state variable _score
. There's a button that randomly changes the _score
value whenever it's clicked.
When the value of the state variable is changed using setState
, the subtree of the state class will be rebuilt. If a widget in the subtree is not cached or is not built using a const constructor, a new instance will be created. That's because Flutter widgets are immutable. However, most likely we don't want the widgets that depend only on the InheritedWidget
to be rebuilt every time the subtree is rebuilt. The solution is using a const constructor to create the widgets. Const constructor only create one instance (canonicalized) even if it's called multiple times using the same arguments (if any).
Since the CurrentScore
widget in the code above is created using a const constructor, it will not be rebuilt again when the subtree is rebuilt. However, because it inherits from Info
widget, it will be notified if updateShouldNotify
returns true. Therefore, putting a widget as the child of an InheritedWidget
can improve efficiency. The child widget doesn't necessarily have to be rebuilt every time its ancestor is rebuilt. If the logic inside updateShouldNotify
determines that the descendant widgets needs to be updated, it will be notified and rebuilt. In addition, the child can also access the properties of the InheritedWidget
.
class _InheritedWidgetExampleState extends State<InheritedWidgetExample> {
final Random _random = Random();
int _score = 10;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Info(
score: _score,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.score),
const CurrentScore(),
],
),
),
OutlinedButton(
child: const Text('Change'),
onPressed: () {
setState(() {
_score = _random.nextInt(100);
});
},
),
],
),
);
}
}
Output:
Beware that in order to get an instance of an Info
widget, the BuildContext
must be put below an Info
widget. Otherwise, Info.of
will return null
. Below is an example that doesn't work.
@override
Widget build(BuildContext context) {
final Info info = Info.of(context);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Info(
score: _score,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.score),
Container(
child: Text(info?.score.toString()),
),
],
),
),
OutlinedButton(
child: const Text('Change'),
onPressed: () {
setState(() {
_score = _random.nextInt(100);
});
},
),
],
),
);
}
Full Code
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Woolha.com Flutter Tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Woolha.com | InheritedWidget Example'),
backgroundColor: Colors.teal,
),
body: InheritedWidgetExample(),
),
);
}
}
class InheritedWidgetExample extends StatefulWidget {
@override
_InheritedWidgetExampleState createState() => _InheritedWidgetExampleState();
}
class _InheritedWidgetExampleState extends State<InheritedWidgetExample> {
final Random _random = Random();
int _score = 10;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Info(
score: _score,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.score),
CurrentScore(),
],
),
),
OutlinedButton(
child: const Text('Change'),
onPressed: () {
setState(() {
_score = _random.nextInt(100);
});
},
),
],
),
);
}
}
class Info extends InheritedWidget {
const Info({
Key key,
@required this.score,
@required Widget child,
}) : assert(score != null),
assert(child != null),
super(key: key, child: child);
final int score;
static Info of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<Info>();
}
@override
bool updateShouldNotify(covariant Info oldWidget) {
return oldWidget.score != score;
}
}
class CurrentScore extends StatelessWidget {
const CurrentScore();
@override
Widget build(BuildContext context) {
print('CurrentScore rebuilt');
final Info info = Info.of(context);
return Container(
child: Text(info?.score.toString()),
);
}
}
Summary
That's how InheritedWidget
works in Flutter. It's the widget to use if you need to propagate information down the tree efficiently . There are a few important things:
- To get the nearest widget that extends
InheritedWidget
with a given type, usedependOnInheritedWidgetOfExactType
method ofBuildContext
. A common convention is to create a method namedof
in the widget that extendsInheritedWidget
. - The return value of
updateShouldNotify
is used to determine whether the widgets that inherit from theInheritedWidget
should be notified. - To make sure that a child widget is not rebuilt every time the tree is rebuilt, it must be cached by using a const constructor.