Flutter State Management - Principle Analysis of InheritedWidget Data Sharing

What is InheritedWidget

This is described in the documentation comments of InheritedWidget:

Base class for widgets that efficiently propagate information down the tree.

Base class for efficiently passing information to subtrees in a render tree.

From the entry of the app:

void main() {
  runApp(MyApp());
  // runApp(CustomInheritedWidget());
}

Start building a rendering tree, MyApp() can be regarded as the root node of the tree, if the Widget of the root node is set to an InheritedWidget, then all the subtree subnodes in its subtree can be obtained in the InheritedWidget shared data. When the data in the InheritedWidget changes, all child widgets that depend on the data will be rebuilt.

What is the use

Around the characteristics of "sharing, co-variation"

  • Unified theme settings, after changing the theme, all subpages will be changed instantly.
  • Project basic data, such as sharing of public data such as user information and permissions.
  • state management
  • ......

core usage process

  • The custom CustomInheritedWidget inherits the InheritedWidget and sets the data to be shared as member variables. Override the updateShouldNotify method, and specify in this method under what circumstances those widgets that rely on this shared data will be rebuilt.
  • Get the shared data in the widget that needs to rely on this data, CustomInheritedWidget.of(context).data.toString(), data is the custom shared data, which can be other. Override the didChangeDependencies method, which is triggered when the shared data changes.
  • Make the dependant a child widget of CustomInheritedWidget

Source code analysis

  • InheritedWidget, the parameter is a child, and the dependant is set to child or the descendant of this child. The core function of Widget is to configure data for element, and InheritedElement here is the class that really works. The updateShouldNotify method is also a method in the overridden InheritedElement, which is used to notify those dependants to update.
abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  /// Whether the framework should notify widgets that inherit from this widget.
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
  • Enter InheritedElement
class InheritedElement extends ProxyElement {
  ...
  final Map<Element, Object> _dependents = HashMap<Element, Object>();

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

  @override
  void debugDeactivated() {
    assert(() {
      assert(_dependents.isEmpty);
      return true;
    }());
    super.debugDeactivated();
  }

  /// Returns the dependencies value recorded for [dependent]
  
  @protected
  Object getDependencies(Element dependent) {
    return _dependents[dependent];
  }

  /// Sets the value returned by [getDependencies] value for [dependent].
  
  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

  /// Called by [dependOnInheritedWidgetOfExactType] when a new [dependent] is added.
  
  @protected
  void updateDependencies(Element dependent, Object aspect) {
    setDependencies(dependent, null);
  }

  /// Called by [notifyClients] for each dependent.
  
  @protected
  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
    dependent.didChangeDependencies();
  }

  /// Calls [Element.didChangeDependencies] of all dependent elements, if
  /// [InheritedWidget.updateShouldNotify] returns true.
  
  @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

  /// Notifies all dependent elements that this inherited widget has changed, by
  /// calling [Element.didChangeDependencies].
 
  @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);
    }
  }
}

_dependents: maintains a map that stores references to all dependents.

**_inheritedWidgets:** This is an attribute of the ancestor class Element. It only works when it is InheritedElement. It saves the correspondence between all InheritedWidgets and InheritedElements that appear in the ancestor node. _inheritedWidgets[widget.runtimeType] = this, here widget.runtimeType is a property in Object, which uniquely represents this class, where this is InheritedElement.

**_updateInheritance():** This method is also a method of Element, which will be called in mount() of Element to update _inheritedWidgets. It should be noted that this method has a default implementation in Element, which is like this:

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

That is to say, determine whether there are _inheritedWidgets in the parent node, and if so, assign it to the _inheritedWidgets of the current element. In this way, the element of each layer will retain the _inheritedWidgets of the previous layer, which is why InheritedWidget can go all the way down Reason for passing the data. And InheritedElement overrides this method. Mainly this code: _inheritedWidgets[widget.runtimeType] = this, update the mapping. Add the current InheritedWidget page to the _inheritedWidgets.

**getDependencies():**Get all dependencies**setDependencies():**Add new dependencies. Generally, when customizing InheritedWidget, a static method of will be defined to obtain the custom InheritedWidget, such as:

static MyInheritedWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(MyInheritedWidget);
  }

Go inside the context.inheritFromWidgetOfExactType method:

InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

Moving on to inheritFromElement():

@override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    return dependOnInheritedElement(ancestor, aspect: aspect);
  }

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

The final fix is: setDependencies(dependent, null); It can be seen that when the dependent uses of to obtain a custom InheritedWidget, it adds itself to the set of dependents. **updateDependencies(): **Update the specified dependant notifyDependent(): , call the didChangeDependencies method of the dependant to inform that the dependent data has changed, this method will be called in batches by the notifyClients() method. notifyClients(): , notify the dependants in batches that the data has changed. This method is defined in ProxyElemnt and overridden in InheritedElement. When the data in the custom InheritedWidget changes, the overridden method updateShouldNotify is used to define whether it needs to notify the dependant to update, updateShouldNotify is as follows:

@override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    // 新旧数据不一致时,返回true,通知依赖本widget的子widget,此时子widget中的didChangeDependencies方法会被调用
    return oldWidget.data != data;
  }

When the new and old data are different, it should be notified. The return here can be defined according to the actual situation. When is the updateShouldnotify method called? Inside the updated method in InheritedElement:

@override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

The updated() method is defined in ProxyElement, where the super.notifyClients() method is called, which is implemented in ProxyElement like this:

 @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

notifyClients() is empty implementation in ProxyELement and rewritten in InheritedElement. This ties the whole process together.

InheritedWidget internal process

  • Customize InheritedWidget, set shared data, rewrite the updateShouldNotify method, and provide the of static method.
  • The dependant becomes the descendant of InheritedWidget. When the dependant calls the of method to obtain the shared data, the dependant is added to the dependant list _dependents internally.
  • During the construction of the element tree, the _inheritedWidgets will be downloaded layer by layer through the _updateInheritance method during mount. During the download process, the widgets of the non-InheritedWidget type will directly assign the parent's _inheritedWidgets to themselves, while the InheritedWidget type widgets will directly assign the _inheritedWidgets of the parent to themselves. The parent's _inheritedWidgets is assigned to itself and added to it.
  • When the shared data changes, the updated method determines whether updateShouldNotify is true. If it is true, it calls the notifyClients method. The notifyClients internally calls dependent.didChangeDependencies(); and finally calls the didChangeDependencies method of the dependant.
  • The shared data of inheritedWidget (custom InheritedWidget) is read-only. If you want to update the data change, you still need to rely on the setState of StatefulWidget (custom StatefulWidget).

  • Cross-page state is not supported.
    If I use InheritedWidget to store data on page A and jump to page B or page C, I will find that I cannot get the InheritedElement of page A using context (page A jumps to page B, page B is not A child node of the page)

Guess you like

Origin blog.csdn.net/jdsjlzx/article/details/123304429