Fluent provides an InheritedWidget for sharing data between widgets. When the InheritedWidget changes, all widgets in its subtree that depend on its data will be rebuilt, which saves developers the trouble of maintaining data synchronization logic. InheritedWidget is a Widget by name, so how does it share data and notify updates? Let's have a look.
The usage of InheritedWidget is divided into the following steps:;
- The custom Widget inherits from inheritedwidwidget, and the custom static of method is used to get the current instance from child
- Implement the updateShouldNotify method in the inheritedwidset class to return the update condition;
- When the data changes, call the setState() method in the State class of the custom widget to trigger the update of the entire InheritedWIdget tree.
The code is not posted. It's quite simple. Let's analyze its principle step by step.
principleInheritedWidget source code
Let's take a look at the source code of inherited widget
abstract class InheritedWidget extends ProxyWidget { const InheritedWidget({ Key? key, required Widget child }) : super(key: key, child: child); @override InheritedElement createElement() => InheritedElement(this); @protected bool updateShouldNotify(covariant InheritedWidget oldWidget); }
He inherited from proxywidget. Let's take a look at proxywidget
abstract class ProxyWidget extends Widget { const ProxyWidget({ Key? key, required this.child }) : super(key: key); final Widget child; }
Through the above source code with a total of no more than 10 lines of code, we can see that there are no other operations inside the inheritedwidwidget except to implement createElement() to create the InheritedElement. Let's continue to look at the InheritedElement
InheritedElement source code
class InheritedElement extends ProxyElement { InheritedElement(InheritedWidget widget) : super(widget); @override InheritedWidget get this Set All dependencies are recorded Element => super.widget as InheritedWidget; //This Set records all dependent elements. The value value here is generally null final Map<Element, Object?> _dependents = HashMap<Element, Object?>(); // This method is invoked in Element mount and activate methods. // _ Inheritedwidwidgets are members of the base class Element, and cache all related inheritedelements in the parent node @override void _updateInheritance() { assert(_lifecycleState == _ElementLifecycle.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; } //This method is called in the update(ProxyWidget oldWidget) method of the parent class ProxyElement. The name is known to be the notification dependent party, which is updated. Here, we first call the rewritten updateShouldNotify method to update it and then traverse it. List the dependencies and call the didChangeDependencies method. In this method, mardNeedsBuild will be called. Therefore, in the next frame drawing process, the corresponding Widget will be rebuilt and the interface will be updated @override void notifyClients(InheritedWidget oldWidget) { for (final Element dependent in _dependents.keys) { notifyDependent(oldWidget, dependent); } } // Update all children void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); } }
We see that InheritedElement also inherits from ProxyElement. There are only three core methods, which are understood according to the notes
Let's continue to see that when the Element is updating, it will call the notifyClients method to traverse and update all the data_ Dependencies, then_ When were the dependencies of InheritedElement added?
The code finally follows up to the Element.dependOnInheritedElement method
@override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) { assert(ancestor != null); _dependencies ??= HashSet<InheritedElement>(); _dependencies!.add(ancestor); //Update in the ancestor.updatedependences method_ Dependencies [dependent], with this as the key ancestor.updateDependencies(this, aspect); return ancestor.widget; } @override T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>() { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T]; if (ancestor != null) { assert(ancestor is InheritedElement); // The dependOnInheritedElement method is called here return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; }
I believe if you have seen it In depth understanding of BuildContext One article or friends who have actually used inherited Widget are certainly familiar with these two methods. For reading the data shared by the parent Widget, the common convention is to define the static of method in the parent Widget class. In this method, the nearest inheritedwidwidget instance in the parent is obtained and returned through the dependOnInheritedWidgetOfExactType, and it is registered to the_ Dependencies, so that when the InheritedWidget of the parent is updated, it will update itself indirectly through the notifyCLients method.
summary- The parent nodes of the InheritedElement cannot find themselves, that is, the data of the InheritedWidget can only be passed from the parent node to the child node, and vice versa.
- If the parent node of a node has more than one InheritedWidget of the same type, the nearest InheritedWidget of this type is obtained by calling dependOnInheritedWidgetOfExactType.