Flutter - Using InheritedWidget Examples

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:

Flutter - InheritedWidget

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, use dependOnInheritedWidgetOfExactType method of BuildContext. A common convention is to create a method named of in the widget that extends InheritedWidget.
  • The return value of updateShouldNotify is used to determine whether the widgets that inherit from the InheritedWidget 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.