Peter
Peter Software Developer | Technical Writer | Actively helping users with their questions on Stack Overflow. Occasionally I post here and on other platforms.

Using Inherited Widget In Flutter

Using Inherited Widget In Flutter
Source Code Follow me on

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.

flutter inherited widget

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:

flutter inherited widget

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!

 

Become a Patron!