Understanding Flutter InheritedWidget through source code

 The core of InheritedWidget is to save the value and the widgets that use this value. By comparing the changes in the value, it determines whether to notify those widgets that use this value to update themselves.

1 updateShouldNotify和notifyClients

InheritedWidget uses the updateShouldNotify function to control whether the subcomponents that depend on it will be rebuilt when InheritedWidget changes: If updateShouldNotify returns true, the subcomponent's build will be called when InheritedWidget changes, and vice versa.

updated method in InheritedElement:

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

InheritedElement inherits from ProxyElement, and the updated implementation of ProxyElement is:

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

The notifyClients implementation of InheritedElement traverses the widgets that rely on itself in _dependents, and then calls its didChangeDependencies for change notification:

  @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();
  }
Element的didChangeDependencies源码如下:

void didChangeDependencies() {
  assert(_active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}

It can be seen that when the InheritedWidget changes, if updateShouldNotify is true, the didChangeDependencies function of the subcomponent will be called through notifyClients, thereby calling markNeedsBuild and adding this Element to the _dirtyElements list. As we all know, _dirtyElements stores elements that need to be rebuilt and will be rebuilt in the next frame, so the subcomponent will be rebuilt in the next frame.

2 Transfer of InheritedWidget

InheritedWidget can be used to allow users to quickly obtain from subcomponents. How is this achieved?

In fact, Flutter Framework also passes InheritedWidget down layer by layer, but because the Framework layer handles it by itself, this process is transparent to us. Let's now sort out the process of InheritedWidget transfer.

In Element, there is a map: _inheritedWidgets. InheritedElements in all superior nodes are saved. Its source code is as follows:

Map<Type, InheritedElement> _inheritedWidgets;

Among them, Type in key is a subclass of InheritedWidget, and value is InheritedElement. Why does the value here save InheritedElement instead of InheritedWidget? From the previous article, we can know that the reference to the corresponding Widget is stored in Element, so the corresponding InheritedWidget can be obtained through InheritedElement. Moreover, the Widget will be rebuilt when the superior Widget is rebuilt, so it is more appropriate to save InheritedElement.

In a normal Element, _inheritedWidgets will directly copy the value of _inheritedWidgets in its parent component. The source code is as follows:

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

In InheritedElement, _inheritedWidgets will first copy the value of _inheritedWidgets in its parent component, and then add itself to the list. The source code is as follows:

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

It can be seen from this that InheritedElement is passed down layer by layer in this way. The _inheritedWidgets assignment process is as follows:

As can be seen from the flow chart, _inheritedWidgets has been assigned when the Element is added to the Element Tree, so it can be accessed in the build function of the subcomponent.

3 InheritedWidget acquisition and registration dependencies

We already know that InheritedElement will be passed to subordinate components, so how to get it? Flutter provides a function dependOnInheritedWidgetOfExactType that specifically obtains an InheritedWidget type. Its source code is as follows:

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

As can be seen from the third line, this function will find the corresponding InheritedElement from _inheritedWidgets and return its InheritedWidget.

In addition to dependOnInheritedWidgetOfExactType, Flutter also provides another function that specifically obtains an InheritedWidget type: getElementForInheritedWidgetOfExactType. Its source code is as follows:

@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;
}

Comparing it with the source code of dependOnInheritedWidgetOfExactType, you can see that dependOnInheritedWidgetOfExactType has more calls to the dependOnInheritedElement function, which is used to create dependencies between InheritedWidget and components that call dependOnInheritedWidgetOfExactType. It has two steps:

  • Add the dependent InheritedElement to the _dependencies list of this Element, which stores all the InheritedElements that this Element depends on.
  • Add this Element to the _dependents map of the dependent InheritedElement. This list stores all Elements that depend on the InheritedElement.

If dependOnInheritedWidgetOfExactType is used, when the dependent InheritedWidget is updated, the dependent subcomponent will be rebuilt; and when getElementForInheritedWidgetOfExactType is used, since the corresponding dependency will not be established, when the InheritedWidget is updated, the dependent subcomponent will not be built. will be rebuilt.

4 Actively call dependOnInheritedWidgetOfExactType

 The subcomponent needs to obtain the InheritedWidget by calling dependOnInheritedWidgetOfExactType, and add itself to the dependency of the InheritedWidget. For convenience, the of method is generally implemented in the InheritedWidget subclass:

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);

  final int data; //需要在子树中共享的数据,保存点击次数

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static ShareDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //如果返回true,则子树中依赖(build函数中有调用)本widget
    //的子widget的`state.didChangeDependencies`会被调用
    return old.data != data;
  }
}

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    print("__TestWidgetState build");
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
    // return Text("tex");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }
}

reference:

Flutter Framework Analysis-InheritedWidget - Zhihu

Guess you like

Origin blog.csdn.net/Mamong/article/details/132569959