Using Inherited Widget In Flutter
In this article, we will explain what is an inherited widget and give an example on how to use it in a Flutter application.
What Is Inherited Widget?
As we have seen in previous tutorials, there are StatelessWidget
and StatefulWidget
. StatelessWidget
is a widget that does not require mutable state, which basically means that it’s static and the widget will not be updated with new data, while StatefulWidget
is a widget that has mutable state. Both of those widgets are immutable, the difference is that StatefulWidget
store the mutable state inside the State
object that is created using createState
method.
When using the above two widgets, you would have a tree of widgets which you can check using the Flutter devtool. To be able to send data to the next widget in the tree, you would have to use the constructor of each widget to pass data from one widget to another.
The InheritedWidget
solves the above issue, the widget will be able to hold data and send data down in the widget tree without even using the constructor of each widget.
Example Using Inherited Widget
Let’s see an example of using InheritedWidget
, using the counter application that is generated when you run flutter create new_project
. We create a new file called inherited_counter.dart
, inside that file we create a class called InheritedCounter
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class InheritedCounter extends InheritedWidget {
InheritedCounter({Key? key, required this.child,required this.counter}) : super(key: key, child: child);
final int counter;
final Widget child;
static InheritedCounter? of(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<InheritedCounter>();
}
@override
bool updateShouldNotify(InheritedCounter oldWidget) {
return oldWidget.counter != counter;
}
}
So first the InheritedWidget
class has the following constructor:
1
2
const InheritedWidget({ Key? key, required Widget child })
: super(key: key, child: child);
As you can see the child
widget is required since the InheritedWidget
has to be at the root of the widget tree so it can send the data through the widget tree.
The of(context)
static method is used to get access to the instance of InheritedCounter
, as you can see inside that method we use context.dependOnInheritedWidgetOfExactType<InheritedCounter>();
which will let use to obtain an instance of the InheritedCounter
, and registers the build context with the widget such that when that widget changes, this build context is rebuilt so that it can obtain new values from that widget.
The updateShouldNotify
method which we override will notify the widgets using the InheritedWidget
if they need to rebuilt or not. Therefore if this method returns true then for example the StatelessWidgets
using this InheritedWidget
will rebuild again.
So now inside the main.dart
file we can do the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@override
Widget build(BuildContext context) {
return InheritedCounter(
counter: 5,
child: Scaffold(
appBar: AppBar(
title: Text(widget.title!),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Builder(builder: (context) {
return Text('${InheritedCounter.of(context)!.counter}');
}),
],
),
),
),
);
}
}
In the above code, we use the InheritedCounter
at the root of the widget tree, we assign 5 to the counter
property, and assign Scaffold
widget to the child
property.
Then to access the counter property inside the Text()
widget, we call the of()
method that will return an instance of InheritedCounter
and then call the property counter
. The following code InheritedCounter.of(context)
is the same as when you use MediaQuery.of(context)
or Theme.of(context)
both of those widgets are using InheritedWidget
. The above code will give us the following screen:
Since the InheritedWidget
is immutable then to update the state (in this case counter value), we need to wrap this InheritedWidget
with a StatefulWidget
which will store the mutable state inside the State
object.
Update State Using Inherited Widget
As I said before, the InheritedWidget
is immutable, therefore we wrap it inside a StatefulWidget
, for example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class InheritedWrapper extends StatefulWidget {
final Widget child;
InheritedWrapper({Key? key, required this.child}) : super(key: key);
static InheritedWrapperState of(BuildContext context) {
return (context.dependOnInheritedWidgetOfExactType<InheritedCounter>())!.data;
}
@override
InheritedWrapperState createState() => InheritedWrapperState();
}
class InheritedWrapperState extends State<InheritedWrapper> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return InheritedCounter(child: this.widget.child, data: this,counter:counter);
}
}
class InheritedCounter extends InheritedWidget {
InheritedCounter({Key? key, required this.child, required this.data,required this.counter})
: super(key: key, child: child);
final Widget child;
final int counter;
final InheritedWrapperState data;
@override
bool updateShouldNotify(InheritedCounter oldWidget) {
return counter != oldWidget.counter;
}
}
The InheritedWrapper
is a custom class that extends StatefulWidget
, since we are going to use the wrapper class in the widget tree. Therefore, we use the method of(context)
which will return an instance of InheritedWrapperState
.
The dependOnInheritedWidgetOfExactType
which takes a generic parameter of type InheritedWidget
will return the instance of InheritedCounter
, therefore we create a variable called data
(can name it whatever you want), that will be of type InheritedWrapperState
. This way when calling InheritedWrapper.of(context)
we will be able to access the properties and method of the InheritedWrapperState
class.
Inside the InheritedWrapperState
, we add the incrementCounter()
method and use setState
to tigger a rebuild when updating the counter
value. So now inside the main.dart
, we have to do the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Widget build(BuildContext context) {
return InheritedWrapper(
child: Scaffold(
appBar: AppBar(
title: Text(widget.title!),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
WidgetB(),
WidgetA(),
WidgetC()
],
),
),
),
);
}
}
We use InheritedWrapper
as a root widget and create three different widgets inside the children
property:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
@override
_WidgetAState createState() => _WidgetAState();
}
class _WidgetAState extends State<WidgetA> {
@override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: onPressed, child: Text("Increment"));
}
onPressed() {
InheritedWrapperState wrapper = InheritedWrapper.of(context);
wrapper.incrementCounter();
}
}
class WidgetB extends StatelessWidget {
const WidgetB({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final InheritedWrapperState state = InheritedWrapper.of(context);
print("widget B");
return new Text('${state.counter}');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final InheritedWrapperState state = InheritedWrapper.of(context);
print("widget C");
return new Text('I am Widget C');
}
}
In all three widgets we get an instance of InheritedWrapperState
by using InheritedWrapperState state = InheritedWrapper.of(context);
. In WidgetA()
we call incrementCounter()
to update the counter value, in WidgetB()
we access the counter
value and display it on the screen.
Now all three widgets are using the InheritedCounter
created before, therefore when clicking on the RaisedButton
, we will get the following output:
1
2
widget B
widget C
widget C
is printed even though nothing is changing and that’s because the dependOnInheritedWidgetOfExactType
will register the widgets that are using the InheritedCounter
, therefore whenever any change happens it will rebuilt those widgets. To solve this we can do the following:
1
2
3
4
5
static InheritedWrapperState of(BuildContext context, {bool build = true}) {
return build
? context.dependOnInheritedWidgetOfExactType<InheritedCounter>()!.data
: context.findAncestorWidgetOfExactType<InheritedCounter>()!.data;
}
We add an optional named parameter that will be equal to true
by default, if it’s true then we call dependOnInheritedWidgetOfExactType()
else we call findAncestorWidgetOfExactType()
. Therefore if we do final InheritedWrapperState state = InheritedWrapper.of(context,build : false);
then findAncestorWidgetOfExactType()
will be called and this case the build context will not rebuild if the return value of this method changes therefore WidgetC()
will not get rebuilt.
I hope you enjoyed reading this flutter tutorial, please feel free to leave any comments or feedback on this post!