What is InheritedWidget and How it works in a Flutter

What is InheritedWidget and How it works in a Flutter?

InheritedWidget is immutable and its attributes are final therefore, Flutter needs to rebuild Inherited Widget if we want to refresh it with new attributes. Also, the Provider plugin uses InheritedWidget. So in this article, we will learn about what is inheritedwidget and how it works in flutter.

When it comes to building robust and efficient user interfaces in Flutter, it’s crucial to grasp the concept of InheritedWidget. Flutter’s InheritedWidget is a powerful tool that allows data to be efficiently propagated down the widget tree, providing an elegant solution to managing state and sharing information across multiple widgets.

What is the purpose of InheritedWidget in flutter?

To pass data from an ancestor widget to descendant ones, which are possibly deep down the widget tree.

How does InheritedWidget Work In a Flutter?

Inherited widget in Flutter
Inheritedwidget in Flutter

Let’s understand with the help of a working example. consider a below screenshot like a below: Widget tree will look like a below:

The widget tree of a example code- Inherited widget in flutter
The widget tree of an example code- Inheritedwidget in flutter
  • The MyContainer Widget can be clicked to increase the counter value in the CounterValue by 1.
  • The CounterLabel Widget is a sibling of the CounterValue widget.
  • The CounterValue should be automatically refreshed with a new counter value whenever the MyContainer is clicked, but the CounterLabel & the DummyContainer should not be rebuilt.

Consider  a code snippet like the below:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'InheritedWidget playground',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: MyStatefulWidget(
          child: MyContainer(
            child: DummyContainer(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  CounterLabel(),
                  CounterValue(),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  final Widget child;

  const MyStatefulWidget({Key key, @required this.child}) : super(key: key);

  static MyStatefulWidgetState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>().data;
  }

  @override
  State<StatefulWidget> createState() {
    return MyStatefulWidgetState();
  }
}

class MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counterValue = 0;

  int get counterValue => _counterValue;

  void addCounterBy1() {
    setState(() {
      _counterValue += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MyInheritedWidget(
      child: widget.child,
      data: this,
    );
  }
}

class MyInheritedWidget extends InheritedWidget {
  final MyStatefulWidgetState data;

  MyInheritedWidget({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}

class MyContainer extends StatelessWidget {
  final Widget child;

  MyContainer({
    Key key,
    @required this.child,
  })  : super(key: key);

  void onPressed(BuildContext context) {
    MyStatefulWidget.of(context).addCounterBy1();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 200,
        height: 200,
        child: RaisedButton(
          color: Colors.red,
          onPressed: (){
            onPressed(context);
          },
          child: child,
        ),
      ),
    );
  }
}

class DummyContainer extends StatelessWidget {
  final Widget child;

  const DummyContainer({Key key, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return child;
  }
}

class CounterLabel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      "Counter",
      style: TextStyle(
        color: Colors.white,
        fontSize: 20,
      ),
    );
  }
}

class CounterValue extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return CounterValueState();
  }
}

class CounterValueState extends State<CounterValue> {
  int counterValue;
  double fontSize;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    MyStatefulWidgetState data = MyStatefulWidget.of(context);
    counterValue = data.counterValue;
    fontSize = 50.0 + counterValue;
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      "$counterValue",
      style: TextStyle(
        fontSize: fontSize,
        color: Colors.white,
      ),
    );
  }
}
  • How does the CounterValue access the current counter value stored in the MyInheritedWidget?
    MyInheritedWidget myInheritedWidget = context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();

    The depend OnInheritedWidget Of ExactType method enables a descendant Widget to access the closest ancestor MyInherited Widget instance enclosed in its BuildContext.

  • How does Flutter find the closest ancestor MyInheritedWidget Instance from a widget’s BuildContext?
    T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
      assert(_debugCheckStateIsActiveForAncestorLookup());
      final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
      if (ancestor != null) {
        assert(ancestor is InheritedElement);
        return dependOnInheritedElement(ancestor, aspect: aspect);
      }
      _hadUnsatisfiedDependencies = true;
      return null;
    }

    Every widget keeps a map _inheritedWidgets, which stores all ancestor Inherited widget instances indexed by their type.

  • How is this _inherited widgets map constructed for each widget’s BuildContext in a widget tree?
  • When an element is added to the widget tree, Flutter calls the mount method, which then invokes the _updateInheritance method, which copies the _inheritedWidgets map from the parent.
  • void _updateInheritance() {
      assert(_active);
      _inheritedWidgets = _parent?._inheritedWidgets;
    }
  • The InheritedElement class overrides _updateInheritance to copy the _inheritedWidgets from the parent and then assigns itself to _inheritedWidgets with the key which is its runtime type.
    void _updateInheritance() {
      assert(_active);
      final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
      if (incomingWidgets != null)
        _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
      else
        _inheritedWidgets = HashMap<Type, InheritedElement>();
      _inheritedWidgets[widget.runtimeType] = this;
    }

    By running the two above code snippets recursively when mounting a widget tree, every widget’s _inheritedWidgets stores all ancestor Inherited Widget instances.

  • when the instance of MyInheritedWidget is rebuilt, the MyContainer and the CounterValue instances are rebuilt while the DummyContainer and CounterValue instances are not rebuilt.
  • The body of the method dependOnInheritedWidgetOfExactType calls dependOnInheritedElement.
    InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
      assert(ancestor != null);
      _dependencies ??= HashSet<InheritedElement>();
      _dependencies.add(ancestor);
      ancestor.updateDependencies(this, aspect);
      return ancestor.widget;
    }

    When the MyInheritedWidget is rebuilt…flutter loops through MyInherited Widget’s dependencies.

    It then decides whether to rebuild these registered widgets by calling the updateShouldNotify method.

    So if the method returns true, Flutter rebuilds all registered widgets. Otherwise, flutter does not.

    void notifyClients(InheritedWidget oldWidget) {
      assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
      for (Element dependent in _dependents.keys) {
        assert(() {
          // check that it really is our descendant
          Element ancestor = dependent._parent;
          while (ancestor != this && ancestor != null)
            ancestor = ancestor._parent;
          return ancestor == this;
        }());
        // check that it really depends on us
        assert(dependent._dependencies.contains(this));
        notifyDependent(oldWidget, dependent);
      }
    }
    void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
      dependent.didChangeDependencies();
    }

By calling didChangeDependencies on a dependent, the dependent is rebuilt.

Conclusion:

Thanks for being with us on a Flutter Journey !!!

Keep Learning !!! Keep Fluttering !!!

So now you also know What is InheritedWidget and How it Works in a Flutter.

Drop us your valuable suggestion/feedback to serve you better.

Abhishek Dhanani

Written by Abhishek Dhanani

Abhishek Dhanani, a skilled software developer with 3+ years of experience, masters Dart, JavaScript, TypeScript, and frameworks like Flutter and NodeJS. Proficient in MySQL, Firebase, and cloud platforms AWS and GCP, he delivers innovative digital solutions.

Leave a comment

Your email address will not be published. Required fields are marked *

Discuss Your Project

Connect with Flutter Agency's proficient skilled team for your app development projects across different technologies. We'd love to hear from you! Fill out the form below to discuss your project.

Have Project For Us

Get in Touch

"*" indicates required fields

ready to get started?

Fill out the form below and we will be in touch soon!

"*" indicates required fields