InheritedWidget

InheritedWidget helps you to propagate data to the widgets down the widget tree. If you wrap your widgets within the Inherited Widget, all the widgets that are below the Inherited Widget can get its data easily. You can also do this using simple constructor through. But it would become very tedious if the widget level goes very deep. Consider the following example where we have class A, B and C. The data that we give it to the class A, passes that data to the class B, and the class B passes the same data to the class C. Then C prints the data.

// A
class A extends StatelessWidget{
  int counter;
  A({this.counter});

  @override
  Widget build(BuildContext context){
    return new B(counter: counter);
  }
}

// B
class B extends StatelessWidget{
  int counter;
  B({this.counter});

  @override
  Widget build(BuildContext context){
    return new C(counter: counter);
  }
}

// C
class C extends StatelessWidget{
  int counter;
  C({this.counter});

  @override
  Widget build(BuildContext context){
    return Text($counter);
  }
}

This is not a very efficient way of doing this specially if your parent-child level goes very deep. There should be a way to share the data to all the widgets. For example you might wanna have a common state that is application specific, and you want to use it wherever you want without passing the data through constructor to constructor. Much like a State management pattern. The Inherited Widget solves this problem. We will see how we can pass the data to its decendent widgets using Inherited Widget.

Creating Inherited Widget

Create a seperate file where you will define your inherited widget. Here is an example how you can create a Inherited Widget.

// app_data.dart;

import 'package:flutter/material.dart';

class AppData extends InheritedWidget{
  int userid;

  AppData({Key key, this.userid, Widget child}) : super(key: key, child: child);

  @override
  bool updateShouldNotify(AppData old) => true;
}

This class is very simple. It extends InheritedWidget class, defines a instance member userid, and overrides the updateShouldNotify method. The only required parameter for Inherited Widget is child, and we pass the child to the parent constructor.

The method updateShouldNotify method always returns true. We will explain about this later on.

Using Inherited Widget

Now you can use this class as an widget:

// class_a.dart

import 'package:flutter/material.dart';
import "app_data.dart";

class ClassA extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return AppData(
      userid: 100,
      child: Center(
        child: ClassB(),
      ),
    ),
  }
}

One important thing to note that we are using the Inherited widget within the ClassA. So only those child widgets that are decendent of the ClassA, will get access to the inherited widget AppData. Any widget that outside of the class ClassA cannot get access to that inherited widget. As ClassA contains ClassB, ClassB will have access to the Inherited widget.

Next we will see how we can access Inherited Widget from ClassB.

Accessing Inherited Widget

// class_b.dart

import 'package:flutter/material.dart';
import "app_data.dart";

class ClassB extends StatefulWidget{
  _ClassBState createState() => _ClassBState();
}
class _ClassBState extends State<ClassB>{
  @override
  Widget build(BuildContext context){
    AppData appdataState = context.dependOnInheritedWidgetOfExactType<AppData>();
    int userid = appdataState.userid;
    return Center(
      child: Text("$userid"),
    );
  }
}

The call to the method dependOnInheritedWidgetOfExactType returns the nearest ancestor Inherited Widget of the type T, in our case the T is AppData. So the method would return the neareset AppData widget or instance. The statement

AppData appdataState = context.dependOnInheritedWidgetOfExactType<AppData>();

Looks very unreadable. And you have to use this long statement everywhere you want to access the widget instance. We will simplyfiy this by overriding a static method of.

// app_data.dart;

import 'package:flutter/material.dart';

class AppData extends InheritedWidget{
  int userid;

  AppData({Key key, this.userid, Widget child}) : super(key: key, child: child);

  static AppData of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<AppData>();
  }

  @override
  bool updateShouldNotify(AppData old) => true;
}

Now you can access the Inherited widget in following way:

AppData appdataState = AppData.of(context);

This is so far the very basic of Inherited Widget. Next we will learn how everything works.

Inherited Widget - Internal

Every widgets that you are creating its either Stateful or Stateless, always have an internal private variable _inheritedWidgets.

Map<Type, InheritedElement> _inheritedWidgets;

This private variable contains all your ancestor Inherited widgets with its corresponding type. When you wrap your widget inside a Inherited Widget, that Inherited widget gets added to the variable _inheritedWidgets and that variable is than passed to its child widgets.

So the code:

return AppData(
  userid: 100,
  child: Center(
    child: ClassB(),
  ),
);

will add the inherited widget AppData to the variable _inheritedWidgets, and pass that variable to its child widget Center and ClassB.

When you call the method dependOnInheritedWidgetOfExactType on the current context, it will use the variable _inheritedWidgets to look for the desired inherited widget. The type after the method dependOnInheritedWidgetOfExactType specifies which type of inherited widget we want to look for. In our case we want to look for AppData inherited widget, thats why the type is necessary after this method.

context.dependOnInheritedWidgetOfExactType<AppData>();

This method will return the specified inherited widget instance.

This method also subscribes the current calling widget to its dependencies. What does that mean? Well, every inherited widget has a private variable _dependents. This variable stores widgets that depends on this inherited widget. In our above example, the class ClassB calls the method dependOnInheritedWidgetOfExactType<AppData>(), that means the widget ClassB depends on this inherited widget class AppData. So a reference of the widget ClassB will get added to the variable _dependents. That's how a inherited widget knows where it is being used. When you change the value of inherited widget, the inherited widget calls didChangeDependencies() and build() methods of its dependent child widgets. That's how whenever you make a change to the inherited widget, the widgets that uses the inherited widget gets rebuild automatically.

But how we can change data of a inherited widget?

One important point to note that Inherited Widget by nature does nothing but holding the data. It doesn't have any mechanism to update the data it contains. That means you can only access the data from Inherited widget but you won't be able to change it. That is the immutable nature of the Inherited widget. But there will be a situation where you want to mutate the data and notify the dependent widgets that the data has changed so that the dependent widget rebuilds itself to update with the changes. You can directly assign values to the Inherited Widget instance like the following:

AppData appdataState = context.dependOnInheritedWidgetOfExactType<AppData>();
appdataState.userid = 100;

This approach will update the data successfully but it will not notify any dependents. There is no way to tell the inherited widget to notify its dependents. So how can we make things work?

The solution is to wrap the Inherited Widget inside a StatefulWidget. In the next lession we will cover this topic.

Inherited Widget with Stateful Widget

The Stateful widget will define some callback along with the data. When you wrap your Inherited Widget inside a Stateful widget, you will pass these data and callbacks from Stateful widgets to Inherited widgets. When you call that method using the inherited widget instance, it will mutate the data using setState(). This will cause the Stateful widget to rebuild. When it rebuilds, it creates a brand new instance of the Inherited Widget. Here is an example:

// app_data.dart;

import 'package:flutter/material.dart';

class AppDataContainer extends StatefulWidget{

  Widget child;
  AppDataContainer({this.child});

  @override
  _AppDataContainerState createState() => _AppDataContainerState();
}

class _AppDataContainerState extends State<AppDataContainer>{

  // Define your app data here --
  int userid;

  // defines your methods here --
  void assignUser(int userId){
    setState((){
      userid = userId;
    });
  }

  @override
  initState(){
    userid = 0;
  }

  @override
  Widget build(BuildContext context){
    return AppData(
      child: widget.child,
      userid: userid,
      assignUser: assignUser
    );
  }
}

class AppData extends InheritedWidget{
  
  int userid;
  ValueChanged<int> assignUser;

  AppData({
    Key key, 
    Widget child,
    this.userid,
    this.assignUser,    
  }) : super(key: key, child: child);

  static AppData of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<AppData>();
  }

  @override
  bool updateShouldNotify(AppData old) => true;
}

The AppData class which extends InheritedWidget hasn't changed much. This time we have added a new callback assignUser as an instance member so that not only you can access the data the inherited widget contains, but you will also be able to call method on the inherited widget instance.

The stateful widget AppDataContainer has one required parameter which is child. This child is passed down to the inherited widget.

In the state class you will define all your instance data and methods. These instance data and methods are passed down to the inherited widget. In the initState method you should initialize your data with the default values. The build method of the state class uses the inherited widget and passes the required data and methods to the inherited widget.

Now you have wrapped your inherited widget within the stateful widget. Now you can use AppDataContainer class to wrap your widgets.

// class_a.dart

import 'package:flutter/material.dart';
import "app_data.dart";

class ClassA extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return AppDataContainer(
      child: Center(
        child: ClassB(),
      ),
    ),
  }
}

When you wrap your widgets within AppDataContainer class you are also wrapping your widgets within inherited widget AppData.

Now use AppData inherited widget to get the instance and call the method to update the data.

// class_b.dart

import 'package:flutter/material.dart';
import "app_data.dart";

class ClassB extends StatefulWidget{
  _ClassBState createState() => _ClassBState();
}
class _ClassBState extends State<ClassB>{
  @override
  Widget build(BuildContext context){
    AppData appdataState = AppData.of(context);
    int userid = appdataState.userid;

    return Center(
      child: InkWell(
        onTap: (){
          appdataState.assignUser(userid + 100);
        },
        child: SizedBox(
          width: double.infinity,
          height: double.infinity,
          child: Center(
            child: Text(userid.toString(), style: TextStyle(fontSize: 30)),
          ),
        )
      ),
    );
  }
}

This is it, now tap on the box, you will see the value changed instantly. When you tap on the box, it calls the method assignUser with a new value. This method is defined in the state class of AppStateContainer which calls setState() method and update the instance property userid. This will cause the state class to call build() method to rebuilt the state class. When the state class is rebuilt, it create a brand new instance of the inherited widget AppData.

When a old inherited widget instance is replaced by a new instance, the framework update the private variable _inheritedWidgets with the mapped key which is in our case AppData. Then it calls the didChangeDependencies() and build() method of each dependent that is assigned to inherited widget AppData.

Thats it, you can have as many data and methods as you want in your state class and if you pass then down to inherited widget, you can access then with the inherited widget instance. This way you can change the data anytime and your UI will always react to the changes instantly.

Limitation

But there is one limitation. When you change the data of the state class, it will always create a brand new instance of the inherited widget which will cause the dependent widgets to rebuilt to update themselves. So even if you change something small, all the dependent will be rebuilt. If your page contains too many dependent widgets, for each changes to the data, all dependent widgets will be rebuilt. Sometimes you want a particular dependent to avoid unncecessary rebuilding when you know that this particular dependent widget doesn't rely on the changes. You will want to rebuilt only those dependent widgets that depends on a particular data.

There should be a way to filter the dependent widgets so that you can decide if a particular dependent widget should respond to the changes. Unfortunately there is no way you can do this with the help of a Inherited Widget.

This limitation can be removed by using InheritedModel widget. InheritedModel widget is a subclass of InheritedWidget class. InheritedModel works the same way as InheritedWidget but it also has a additional functionality to filter out the dependents before it calls didChangeDependencies() method. We will learn how to do that with InheritedModel widget in the next lesson.

InheritedModel

The implementation of the InheritedModel is almost same as InheritedWidget. Here is the implementation of the InheritedModel that users the previous example.

import 'package:flutter/material.dart';


enum AspectsList{
  USERID_CHANGED,
  COUNTER_CHANGED,
}

class AppDataContainer extends StatefulWidget{

  Widget child;
  AppDataContainer({this.child});

  @override
  _AppDataContainerState createState() => _AppDataContainerState();
}

class _AppDataContainerState extends State<AppDataContainer>{

  // Define your app data here --
  int userid;
  int counter;

  // defines your methods here --
  void assignUser(int userId){
    setState((){
      userid = userId;
    });
  }

  void assignCounter(int value){
    setState((){
      counter = value;
    });
  }

  @override
  initState(){
    userid = 0;
    counter = 0;
  }

  @override
  Widget build(BuildContext context){
    return AppData(
      child: widget.child,
      userid: userid,
      counter: counter,
      assignUser: assignUser,
      assignCounter: assignCounter,
    );
  }
}

class AppData extends InheritedModel<AspectsList>{
  
  int userid;
  int counter;
  ValueChanged<int> assignUser;
  ValueChanged<int> assignCounter;

  AppData({
    Key key, 
    Widget child,
    this.userid,
    this.counter,
    this.assignUser,    
    this.assignCounter,
  }) : super(key: key, child: child);

  static AppData of(BuildContext context, {AspectsList aspect}){
    return InheritedModel.inheritFrom<AppData>(context, aspect: aspect);
  }

  @override
  bool updateShouldNotify(AppData old) => true;

  @override
  updateShouldNotifyDependent(AppData old, Set<AspectsList> aspect){
    bool changed = false;
    if(aspect.contains(AspectsList.USERID_CHANGED)){
      changed = changed || old.userid != userid;
    }
    if(aspect.contains(AspectsList.COUNTER_CHANGED)){
      changed = changed || old.counter != counter;
    }

    return changed;
  }
}

Implementation Details

First consider the AppData class that extends InheritedModel class. The implementation is almost same as InheritedWidget except few things:

Specify the type of aspect in class Declaration

Specify the type of the aspect that you are going to use after the class name InheritedModel. Here we have used a enum AspectsList type as the type of the aspect.

class AppData extends InheritedModel<AspectsList>

But what is aspect? You can think of it as a key or keyword. The aspect specifies what kind of change the data of the InheritedModel instance took place. Each aspect should be bound to each data member. For userid data member there is a USERID_CHANGED constant and for counter data member there is a COUNTER_CHANGED constant defined in the enum AspectsList. Leter we will use these aspects to determine if a particular data member has changed or not.

Each dependent of a InheritedModel widget is bound to one or more aspect. If your dependent widget depends on more than one data member, you should specify each aspect for each data member. The above program is currently configured to accept only one aspect. But later we will see how we can configure our example to make it accept multiple aspects.

Everytime you replace an old InheritedModel instance with a new instance, flutter calls a method updateShouldNotifyDependent for each dependent widget to determine if the data members that the dependent widget depends on has changed or not. The method updateShouldNotifyDependent is called with the aspects that the dependent widget is bound to.

InheritedModel uses aspects to check if a particular dependent widget should be rebuild. You can also use String, or int, but in our example we have defined it as a enum type. So that the list of aspects can be seen in one place and will be easier to maintain and it is much more redable. Leter we will see how to bind aspects with the dependent widget.

Use inheritFrom

Coming next, another difference is that we are using InheritedModel.inheritFrom<T>() method to look for nearest InheritedModel widget instance instead of dependOnInheritedWidgetOfExactType() method. This is important for InheritedModel widget. You also need to specify the type of the InheritedModel that we are going to search for. In our case we are going to look for AppData InheritedModel widget, that's why there is type mentioned after the method name.

InheritedModel.inheritFrom<AppData>(context, aspect: aspect);

This method accepts two argument, the first argument is positional argument which is conext, and second argumen is a named argument aspect. The argument aspect is optional, that is why in the parameter list of of method, we have wrapped it within the curly bracket which makes it named optional parameter.

static AppData of(BuildContext context, {AspectsList aspect}){
  return InheritedModel.inheritFrom<AppData>(context, aspect: aspect);
}

Override updateShouldNotifyDependent

Lastly, another notable difference is that we also have to override the method updateShouldNotifyDependent. This method is responsible for checking if a dependent should be updated. This method has two parameter, the first parameter is the old instance of InheritedModel widget, and second parameter is a Set of aspects. As our aspect type is AspectsList, thats why our second parameter is typed as Set<AspectsList>.

updateShouldNotifyDependent(AppData old, Set<AspectsList> aspect){

That is all about implementing the InheritedModel widget, the rest of the code is same.

Using the InheritedModel

Using the InheritedModel is same as InheritedWidget. The class ClassA is same as before:

// class_a.dart

import 'package:flutter/material.dart';
import "app_data.dart";

class ClassA extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return AppDataContainer(
      child: Center(
        child: ClassB(),
      ),
    ),
  }
}

Accessing InheritedModel

You can access the InheritedModel widget the same way you have done it with InheritedWidget, excepts you need to pass the aspect as optional argument. Here is a different example of ClassB:

import 'package:flutter/material.dart';
import "app_data.dart";

class ClassB extends StatefulWidget{
  @override
  _ClassBState createState()=> _ClassBState();
}

class _ClassBState extends State<ClassB>{

  @override
  Widget build(BuildContext context){

    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          UserBox(),
          SizedBox(width: 100),
          CounterBox(),
        ]
      ),
    );
  }
}


class UserBox extends StatefulWidget{
  @override
  _UserBoxState createState() => _UserBoxState();
}
class _UserBoxState extends State<UserBox>{
  @override
  Widget build(BuildContext context){

    print("User");

    AppData model = AppData.of(context, aspect: AspectsList.USERID_CHANGED);
    int userId = model.userid;

    return DecoratedBox(
      decoration: BoxDecoration(color: Colors.red),
      child: InkWell(
        child: SizedBox(
          width: 100,
          height: 100,
          child: Center(
            child: Text(userId.toString(), style: TextStyle(fontSize: 30, color: Colors.white)),
          ),
        ),
        onTap: (){
          model.assignUser(userId + 1);
        }
      ),        
    );
  }
}


class CounterBox extends StatefulWidget{
  @override
  _CounterBoxState createState() => _CounterBoxState();
}
class _CounterBoxState extends State<CounterBox>{
  @override
  Widget build(BuildContext context){

    print("Counter");

    AppData model = AppData.of(context, aspect: AspectsList.COUNTER_CHANGED);
    int counter = model.counter;

    return DecoratedBox(
      decoration: BoxDecoration(color: Colors.pink),
      child: InkWell(
        child: SizedBox(
          width: 100,
          height: 100,
          child: Center(
            child: Text(counter.toString(), style: TextStyle(fontSize: 30, color: Colors.white)),
          ),
        ),
        onTap: (){
          model.assignCounter(counter + 1);
        }
      ),        
    );
  }
}

Notice how we are accessing the InheritedModel using the following statement:

AppData.of(context, aspect: AspectsList.COUNTER_CHANGED);

It is quite same as InheritedWidget but only difference is that we are also passing aspect. When you call this method, the widget is going to be stored as dependent widget of the InheritedModel AppData in the _dependent variable. It also stores the passed aspect along with the dependent widget in the _dependent variable. This way the InheritedModel widget instance tracks its dependent widget. When you create anew instance of the same type of InheritedModel and replace it in place of old one, the flutter calls updateShouldNotify method of the InheritedModel widget, if that returns false, the dependents widgets will not be notified at all. If this method returns true, then flutter calls the method updateShouldNotifyDependent for each and every dependent widget seperately and passes two argument to that method, the first parameter is the old instance of the InheritedModel widget, which is in our case AppData instance, and second parameter is the dependent widget's corresponding aspect. The aspect is passed as a Set.

updateShouldNotifyDependent(AppData old, Set<AspectsList> aspect){
  bool changed = false;
  if(aspect.contains(AspectsList.USERID_CHANGED)){
    changed = changed || old.userid != userid;
  }
  if(aspect.contains(AspectsList.COUNTER_CHANGED)){
    changed = changed || old.counter != counter;
  }

  return changed;
}

In that method we are checking if the dependent widgets aspect matches any of the contant of the AspectsList enum, if it does, then we are checking if a particular instance field has changed or not, and according to it if this method returns true, the dependent widget will rebuilt and if returns false, it will skip the rebuilding.

If you do not provide the aspect like the following statement, the method updateShouldNotifyDependent will not be called for that dependent widget. Because if you don't supply aspect, its aspect is null, in this case the dependent widget will be rebuilt unconditionally.

AppData model = AppData.of(context);

Multiple Aspects

So far we have discussed the InheritedModel which is configured to accept only one aspect. You cannot pass multiple aspects. Even though the doc says that a dependent widget may have multiple aspects but it is not clear how to do that. But the following solution works anyway.

First change the type of the aspect in the InheritedModel class declaration:

class AppData extends InheritedModel<Set<AspectsList>>{
}

Now our model will accept a set of AspectsList enum constant. Next update the of method. In the parameter list, change the type from AspectsList to Set<AspectsList>

static AppData of(BuildContext context, {Set<AspectsList> aspect}){
  return InheritedModel.inheritFrom<AppData>(context, aspect: aspect);
}

Now you can pass a set to the of method as the second argument. Next, update the updateShouldNotifyDependent method.

updateShouldNotifyDependent(AppData old, Set<Set<AspectsList>> aspect){
  bool changed = false;
  Set<AspectsList> widgetAspects = aspect.elementAt(0);
  if(widgetAspects.contains(AspectsList.USERID_CHANGED)){
    changed = changed || old.userid != userid;
  }
  if(widgetAspects.contains(AspectsList.COUNTER_CHANGED)){
    changed = changed || old.counter != counter;
  }

  return changed;
}

Now you should always pass a set as the aspect. For example, if your widget depends on both userid and counter data field of the model, then you should use two aspects, USERID_CHANGED for userid, and COUNTER_CHANGED for counter member. If any of these member changes updateShouldNotifyDependent will return true for that widget.

AppData model = AppData.of(context, aspect: {AspectsList.USERID_CHANGED, AspectsList.COUNTER_CHANGED});

This way you can specify that your dependent widget depends on more than one aspect. If any of the aspects matches in updateShouldNotifyDependent method, it checks if the corresponding data member has changed or not.

Summary

This is all about InheritedWidget and InheritedModel.