Definitions and Usage

This article only analyzes the InheritedWidget and requires some basic knowledge, including but not limited to:

  1. The basics of Flutter;
  2. Widget, Element relationship

A basic Widget that efficiently delivers information along a tree. . Users can use BuildContext dependOnInheritedWidgetOfExactType access to certain types of recently InheritedWidget instance, after every time the referenced InheritedWidget status changes, Causes the referrer to rebuild.

Here’s a quick overview of what we use in this tutorial (we’re not going to use the InheritedWidget here) :

  • So let’s define aInheritedWidgetType of Widget:
// Official Demo class FrogColor extends InheritedWidget {const FrogColor({Key Key, @required this.color, @required Widget child, }) : assert(color ! = null), assert(child ! = null), super(key: key, child: child); final Color color; static FrogColor of(BuildContext context) {returncontext.dependOnInheritedWidgetOfExactType<FrogColor>(); } @override bool updateShouldNotify(FrogColor old) => color ! = old.color; }Copy the code
  • Add the Widget as a normal Widget to the Widget tree;
  • Used in any of its child widgetsFrogColor.of(context).colorCan obtaincolorInformation.

There are two key points to refresh child widgets:

  1. How do child widgets get across widgetsInheritedWidget
  2. InheritedWidgetHow to notify child Widgets refresh when data changes (Build)

These two points are the core and key to InheritedWidget, and we will analyze the following two issues:

InheritedWidgetPass through the tree

In the example code above, the only code that is visible and associated with the InheritedWidget in the child Widget is frogcolor.of (context).color. The key method is dependOnInheritedWidgetOfExactType, the method is defined in BuildContext, and have concrete implementations in the Element. Check the BuildContext. DependOnInheritedWidgetOfExactType Element in the source code is as follows:

Map<Type, InheritedElement> _inheritedWidgets;

@override
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) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}

Copy the code

As you can see, each Element instance holds a _inheritedWidgets, and whenever a particular type of dependency is added to a Widget, an InheritedElement instance of the related type is pulled from the collection. When does _inheritedWidgets save the InheritedElement? Next, look at the Element source to see when the _inheritedWidgets are assigned. In Element there is a _updateInheritance method:

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

It is called when the mount and activate functions are executed. This means that every time An Element is mounted and re-enabled, Element packages all of its inheritedElements from its upper element and takes them in. InheritedElement inherits Element and rewrites _updateInheritance

@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

In contrast to a regular Element, an InheritedElement not only takes all of its upper Element’s inheritedElements, but also adds itself to the collection as an Element. As you can see, each Element holds a collection that holds all of the inheritedElements present in its upper Element, and each time an InheritedElement appears, that InheritedElement adds itself to the collection and passes it down. This is exactly how inheritedWidgets ensure that the underlying controls have access to all of the inheritedWidgets in their upper layer.

How do inheritedWidgets refresh

When InheritedWidget state changes, it is how to inform the subtree refresh, we continue to analyze dependOnInheritedWidgetOfExactType method code, When an instance ancestor of a specific type can be pulled from _inheritedWidgets. Implement the dependOnInheritedElement method:

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

InheritedElement calls its own updateDependencies method and passes the current Element instance to it:

final Map<Element, Object> _dependents = HashMap<Element, Object>();

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

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

InheritedElement holds all widgets that are dependent on that instance through a _dependents collection. Every time a Widget builds a dependency on an InheritedElement, it adds itself to the InheritedElement collection. When InheritedElement is updated, notifyClients is executed on the instance:

@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;returnancestor == this; } ()); // check that it really depends on us assert(dependent._dependencies.contains(this)); // Iterate over all Element notifyDependent(oldWidget, dependent); } } @protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {/ / in turn calls all child Element method didChangeDependencies dependent. DidChangeDependencies (); }Copy the code

The markNeedsBuild method is called in Element’s didChangeDependencies method, which marks the current element and re-creates the Widget when the next frame arrives, outside the scope of the InheritedWidget. There is no discussion here.

conclusion

The key core of InheritedWidget is two collections. The inheritedWidgets in all upper-layer components are stored in a collection passed down the tree so that the widgets can get the Inheritedwidgets in any upper-layer tree. On the other hand, the InheritedWidget holds all dependent elements through a collection that is added to the collection every time an element is dependent on it. When the state of the InheritedWidget changes, the collection is iterated over and the didChangeDependencies method for each element is executed.