Flutter State Management--An Analysis of the Principle of InheritedWidget Data Sharing

What is InheritedWidget

This is described in the documentation notes for InheritedWidget:

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

Base class that effectively conveys information to subtrees in the rendering tree.

Access from app:

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

Start build ing a rendering tree that MyApp() can see as the root node of the tree. If the root node's Widget is set to an InheritedWidget, all the subtree sub-nodes in the subtree can get the data shared within the InheritedWidget. When the data in the InheritedWidget changes, all child widgets that depend on that data will be rebuilt.

What's the usage?

Features around Sharing and Covariance

  • Unify theme settings so that when you change the theme, all subpages change immediately.
  • Sharing of project base data, such as user information, permissions, and other public data.
  • State Management
  • ......

Core usage process

  • Custom CustomInheritedWidget inherits InheritedWidget and sets the data to be shared as a member variable. Override the updateShouldNotify method, which specifies when widget s that depend on this shared data will be rebuilt.
  • Get the shared data in a widget that needs to rely on it, CustomInheritedWidget.of(context).data.toString(), data is custom shared data and can be other. Override the didChangeDependencies method, which is triggered when the shared data changes.
  • Make Dependent a child widget of CustomInheritedWidget

Source Code Analysis

  • InheritedWidget, parameter is a child, sets the dependency to child or the child's descendant. The core function of Widgets is to configure data for element ation, where InheritedElement is the class that really works. The updateShouldNotify method is also a method in the overridden InheritedElement to notify those who depend on it 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 holds references to all dependents.

**_ inheritedWidgets:**This is a property of the ancestor class Element, which only works if it is InheritedElement, which holds the corresponding relationship between inheritedWidgets and InheritedElements that occur in the ancestor node_ inheritedWidgets[widget.runtimeType] = this, where widgets. RuntimeType is an attribute in Object and represents this class only, where this is the InheritedElement.

**_ updateInheritance():** This method is also an Element method, which is called within the mount() of the Element to update _ inheritedWidgets. It is important to note that this method has a default implementation in Element as follows:

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

That is, to determine if there is _in the parent node inheritedWidgets, if any, assigned to the _of the current elementation inheritedWidgets, so that each layer's elementation retains the previous layer's _ inheritedWidgets, which is why inheritedWidgets keep passing data down. InheritedElement overrides this method. Mainly this code: _ inheritedWidgets[widget.runtimeType] = this, update mapping. Add the current InheritedWidget page to the _ inheritedWidgets.

**getDependencies():**Get all dependencies**setDependencies():**Add new dependencies. When customizing an InheritedWidget, a static method of is defined to get the custom InheritedWidget, such as:

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

Go deeper into context. Inside the 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;
  }

Continue deep into 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: set Dependencies (dependent, null); Thus, when a dependency uses of to get a custom InheritedWidget, it adds itself to the dependency collection. ** updateDependencies():** Update the specified dependency notifyDependent():, call the dependency didChangeDependencies method, notify that the dependent data has changed, and this method will be called in bulk by the notifyClients() method. notifyClients():, Bulk inform the dependant that the data has changed. This method is defined in ProxyElemnt and overridden in InheritedElement. When data changes within a custom InheritedWidget, an override method, updateShouldNotify, is used to define whether a notification dependency update is required, such as:

@override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    // Returns true when old and new data are inconsistent, notifies the child widget that depends on this widget, and the didChangeDependencies method in the child widget is called
    return oldWidget.data != data;
  }

Notifications are made when old and new data are different, where return can be defined based on actual conditions. So when does the updateShouldnotify method call? Within the updated method in the InheritedElement:

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

The updated() method is defined in the ProxyElement, where super is called. The notifyClients () method is implemented in ProxyElement as follows:

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

NotfyClients () is an empty implementation in ProxyELement and is overridden in InheritedElement. This brings the whole process together.

InheritedWidget internal process

  • Customize the InheritedWidget, set shared data, override the updateShouldNotify method, and provide of static methods.
  • Make a dependency a descendant of InheritedWidget, which is internally added to the list of dependencies when the dependency calls the of method to get shared data dependents.
  • The element tree is built by mount ing through _ updateInheritance method_ inheritedWidgets layer-level download, in which a widget of type other than InheritedWidget will send parent's _ inheritedWidgets are assigned directly to themselves, while widgets of type InheritedWidget will assign parent's _ inheritedWidgets give themselves and add them in.
  • Shared data changes, the updated method determines whether updateShouldNotify is true, calls the notifyClients method for true, and notifyClients calls dependent internally. DidChangeDependencies(); The call is ultimately made to the didChangeDependencies method of the dependency.

Keywords: Android Android Studio Flutter

Added by jenreb1 on Sat, 05 Mar 2022 19:20:44 +0200