InheritedWidget introduction

When developing the interface of Flutter, we often encountered problems with data transfer. Because Flutter uses a tree of nodes to organize pages, a normal page has a very deep node hierarchy. At this point, if we still pass data layer by layer, it will be very troublesome when we need to obtain data of multiple parent nodes. Because of these problems, Flutter provides us with an InheritedWidget that gives all the children of a node access to the data under that node. The Scoped Model, BloC, and Provider are implemented based on InheritedWidget.

The source code used in this article is linked to a video tutorial

InheritedWidget source code analysis

As you can see, the source code for the InheritedWidget is very simple.

/// Abstract class InheritedWidget => Proxywidget => Widget
abstract class InheritedWidget extends ProxyWidget {
  /// The constructor
  /// Because the InheritedWidget is a Widget without an interface, the actual Widget needs to be passed in
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  /// Overrides the Widget createElement method of the superclass
  @override
  InheritedElement createElement() => InheritedElement(this);

  /// Called when changes are made in the parent or ancestor widget (updateShouldNotify returns true).
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
Copy the code

InheritedWidget sample

Let’s use the InheritedWIdget as an example of an official counter

Step 1: Define an InheritedState class

  1. createInheritedStateInherited fromInheritedWidget
  2. Define a data that needs to be sharedcount
  3. Define one for external fetchingInheritedStateThe instanceofmethods
  4. rewriteupdateShouldNotifyMethod, whose primary purpose is to notify children that depend on the tree to share datawidget
import 'package:flutter/material.dart';

class InheritedState extends InheritedWidget {

  /// A constructor
  InheritedState({
    Key key,
    @required this.count,
    @required Widget child
  }): assert(count ! =null),
    super(key:key, child: child);

  /// Data to be shared
  final int count;

  /// Gets the most recent current InheritedWidget for the component
  static InheritedState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedState>();
  }

  /// Notify child widgets that rely on the tree to share data
  @override
  bool updateShouldNotify(covariant InheritedState oldWidget) {
    return count != oldWidget.count;
  }

}
Copy the code

Step 2: Write the layout and implement the counter effect with the InheritedWidget

  1. I created one firstInheritedCountComponents and subcomponentsWidgetAandWidgetB.
  2. inInheritedCountTo define acountVariable for childrenwidgetTo get thecountThe data.
  3. useInheritedStateComponent and pass incountValue, and subcomponents.
  4. Child componentInheritedStateShared data in.
  5. inInheritedCountButton click changecountValue, the child component data will be refreshed.
import 'package:flutter/material.dart';
import 'package:flutter_code/InheritedWidget/InheritedState.dart';

class InheritedCount extends StatefulWidget {
  @override
  _InheritedCountState createState() => _InheritedCountState();
}

class _InheritedCountState extends State<InheritedCount> {

  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedDemo"), ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { _count++; }); }, child: Icon(Icons.add, color: Colors.white,), ), body: Center( child: InheritedState( count: _count, child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.center, children: [ WidgetA(), WidgetB() ], ) ), ), ); }}class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("widget text"); }}class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    returnText(InheritedState.of(context)? .count.toString(), style: TextStyle( color: Colors.green, fontSize:50)); }}Copy the code

InheritedWidget source code analysis

In the counter example above, what is inheritedState.of (context) associated with WidgetB and InheritedWidget? . Count. The toString (), one of the most critical is the context. DependOnInheritedWidgetOfExactType (), We see dependOnInheritedWidgetOfExactType () Element in the source code is as follows: the code is in the framework. The dart line 3960

Map<Type, InheritedElement> _inheritedWidgets;

@override
  T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
   	/// Assertion, used to detect if there are any ancestors in use (active) while debugging
    assert(_debugCheckStateIsActiveForAncestorLookup());
    /// Access to the_inheritedWidgets Array data
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
    if(ancestor ! =null) {
      // assert whether the current ancestor is an InheritedElement type
      assert(ancestor is InheritedElement);
      // Returns and calls the update method
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }
Copy the code

We can see that every Element instance holds a _inheritedWidgets, and every time we use this method, we take the InheritedElement instance of the related type from the collection object. So we don’t see how to set _inheritedWidgets in this method, so let’s look at how _inheritedWidgets are assigned.

// Element  
void _updateInheritance() {
    assert(_active); _inheritedWidgets = _parent? ._inheritedWidgets; }Copy the code

In the _updateInheritance method, we first assert whether the current node is active or not, and then assign values to the _inheritedWidgets of the parent node. We continue to look at the situation when _updateInheritance is called:

  @mustCallSuper
  void mount(Element parent, dynamicnewSlot) { ...... _updateInheritance(); . }@mustCallSuper
  voidactivate() { ...... _updateInheritance(); . }Copy the code

We can see that in Element it calls the mount and activate functions, which means it is called every time an Element is mounted and remounted. When this method is executed, Element takes all of the inheritedElements from the upper layer. [InheritedElement] InheritedElement overrides the _updateInheritance method [InheritedElement]

  @override
  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;
  }

Copy the code

How is InheritedWidget refreshed

InheritedElement takes all of the InheritedElment of the parent class and passes it down, and inheritedWidgets make all of the upper-layer inheritedWidgets accessible to the child widgets. So how does it refresh? In our Element dependOnInheritedWidgetOfExactType method calls the dependOnInheritedElement method, the code is as follows:

Set<InheritedElement> _dependencies;

@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
  assert(ancestor ! =null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);
  return ancestor.widget;
}

@protected
void updateDependencies(Element dependent, Object aspect) {
  setDependencies(dependent, null);
}

@protected
void setDependencies(Element dependent, Object value) {
  _dependents[dependent] = value;
}
Copy the code

You can see that the InheritedElement instance calls its own updateDependencies method and passes the current Element instance to it

  /// Called during build when the [widget] has changed.
  ///
  /// By default, calls [notifyClients]. Subclasses may override this method to
  /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
  /// widgets are equivalent).
  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final 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); }}}@protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }
Copy the code

Because when InheritedElement is updated, the updated method is executed and notifyClients is called, iterating over all elements and calling the didChangeDependencies method.