What is InheritedWidget and How it works in a Flutter??

· 7 min read
What is InheritedWidget and How it works in a Flutter
What is InheritedWidget and How it works in a Flutter

 

Inherited Widget 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.

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.

FlutterAgency.com is our portal Platform dedicated to Flutter Technology and Flutter Developers. The portal is full of cool resources from Flutter like Flutter Widget GuideFlutter ProjectsCode libs and etc.

FlutterAgency.com is one of the most popular online portals dedicated to Flutter Technology and daily thousands of unique visitors come to this portal to enhance their knowledge of Flutter.

Leave a Reply