This is the 8th day of my participation in the August More text Challenge. For details, see: August More Text Challenge
preface
In the last article, we looked at the setState process from a source perspective to see why the entire Widget tree is rebuilt when the setState method is called. However, just because the Widget tree is rebuilt does not mean that the render element tree needs to be rebuilt, in fact the render tree is only updated, not necessarily removed after rendering.
However, our ModelBinding class also uses setState for state updates. Why isn’t its child component rebuilt instead of updating the build method of the child component that depends on the state? With the exception of wrapping the child components with an internal InheritedWidget, it’s no different than a regular StatefulWidget. As in the previous two analyses about state management from the InheritedWidget, the difference is in the InheritedWidget. In the spirit of being technical, let’s take a look at how InheritedWidget makes a different call to setState.
Know how and why. Before reading this article, if the management of Flutter status is not clear, it is recommended to read the previous articles for some background:
- Introduction and Combat (38) : Stateless and Stateless Components from a blind date
- Introduction to Flutter and Combat (39) : A detailed explanation of the render mode
- Introduction and Practice (40) : State management with shopping cart as an example
- Learn about status management from InheritedWidget (1)
- Learn about status management from InheritedWidget (2)
- Introduction to Flutter and Practice (43) : setState and ModelBinding usage comparison
- Introduction to Flutter and Actual Combat (44) : What happens when you analyze setState from the source code?
The difference between InheritedWidget and StatefulWidget
First of all,InheritedWidget
和 StatefulWidget
The inheritance chain is different. The comparison is as follows.InheritedWidget
Inherited fromProxyWidget
After that,Widget
And theStatefulWidget
Direct inheritanceWidget
. The second is that the render element classes created are different,InheritedWidget
的 createElement
Returns theInheritedElement
And theStatefulWidget
的 createElement
Returns theStatefulElement
.
As we saw in the last article, the actual rendering control is done by the Element class. In fact, the Widget’s createElement method passes the Widget object to the Element object. It is up to the Element object to decide how to render based on the Widget’s component configuration.
The definition of an InhretiedWidget is simple, as follows:
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);
}
Copy the code
The updateShouldNotify method is used for a subclass implementation of InheritedWidget to decide whether to notify its child component (widget). For example, if the data has not changed (a typical pull-down refresh has no new data), then you can return false, eliminating the need to update the child components and reducing the performance cost. Our previous ModelBinding example simply returned true, meaning that the child component was notified every time there was a change. The next step is to see the difference between InheritedElement and StatefulElement.
The difference between InheritedElement and StatefulElement
We looked at StatefulElement in the last post, which calls the rebuild method performRebuild after setState. The performRebuild method is implemented in the parent Component. The core is that when the Widget tree changes, the updateChild method is called to update the child elements based on the new Widget tree.
When the ModelBinding in the previous article called setState, since it is itself a StatefulWidget, it will no doubt also call updateChild to update the child elements. Judging from the execution results, since the Widget tree was not rebuilt in the ModelBinding example, the processing should be different before updateChild. The component’s build method is called before updateChild to get the new Widget tree. Is it different here? Keep reading.
In addition to InheritedWidget, there is an additional layer of inheritance on top of InheritedElement, which is called ProxyElement. It is in ProxyElement that we find the build method. Unlike StatefulElement, the build method here does not call the build method of the corresponding Widget object and returns widget.child directly.
// The build method of ProxyElement
@override
Widget build() => widget.child;
// Build method of StatefulElement
@override
Widget build() => state.build(this);
// Build method of StatelessElement
@override
Widget build() => widget.build(this);
Copy the code
So we know whyInheritedWidget
Why is it that the child component tree is not rebuilt during the status updateProxyElement
Instead of rebuilding, the constructed subcomponent tree is returned directly in the Did you think you knew the truth? Say goodwarren? Shouldn’t we be asking if the subcomponent tree changes,ProxyElement
How is it perceived? If a new element is inserted, or an element’s render parameters change (color, font, content, etc.), how does the render layer know? Keep going!
How does InheritedElement perceive changes in the component tree
Take a look at the class structure of InheritedElement.
classDiagram Element <-- ComponentElement ComponentElement <-- ProxyElement ProxyElement <-- InheritedElement class Element { -dependOnInheritedWidgetOfExactType() -dependOnInheritedElement() } class InheritedElement { -Map<Element, Object? > _dependents -void _updateInheritance() -getDependencies(Element dependent) setDependencies(Element dependent, Object? value) updateDependencies(Element dependent, Object? aspect) notifyDependent(covariant InheritedWidget oldWidget, Element dependent) updated(InheritedWidget oldWidget) notifyClients(InheritedWidget oldWidget) } class ProxyElement { -build() -update(ProxyWidget newWidget) -updated(covariant ProxyWidget oldWidget) -notifyClients(covariant ProxyWidget oldWidget) }
It is also not complex in terms of class structure, since most of the rendering management is already done in the parent ComponentElement and Element. We’ve covered the build method, but let’s focus on what happens after the InheritedWidget’s parent calls setState. When a subcomponent needs to obtain state management, we use the following method:
ModelBindingV2.of<FaceEmotion>(context)
Copy the code
This method actually calls:
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
Copy the code
DependOnInheritedWidgetOfExactType methods defined in BuildContext here, but is actually a Element. You access a HashMap object called inheritedWidgets and find an InheritedElement of the corresponding type in the array.
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor,
{Object? aspect}) {
assert(ancestor ! =null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies! .add(ancestor); ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@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
This array is actually initialized in the call to _updateInheritance in the mount method. You override the method of Element in your InheritedElement. When you create an InheritedWidget, you associate an InheritedElement with the corresponding component runtime type in the mount.
@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;
}
Copy the code
The method first InheritedWidgets of the parent and then saves itself (InheritedElement) to the HashMap so that the element can be found later.
Therefore, when using dependOnInheritedWidgetOfExactType in child components, is actually executed dependOnInheritedElement method, The parameters passed are the InheritedElement Element found by type and the specified InheritedWidget type parameter aspect, in this case our _ModeBindScope
, and then bind the current render Element (an Element subclass) to it. Tell the InheritedElement object that the component depends on its InheritedWidget. We can see that there is such an object in _dependents. In this way, inheritedElements are associated with the corresponding rendering element of the component.
The next step is to get the new component tree and update the component after looking at setState. We already know that setState calls performRebuild, and inside performRebuild calls the Element’s updateChild method, Now let’s see what InheritedElement’s updateChild does. UpdateChild actually calls the child.update(newWidget) method:
else if (hasSameSuperclass &&
Widget.canUpdate(child.widget, newWidget)) {
if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget);/ /...
newChild = child;
}
// ...
return newChild;
Copy the code
In ProxyElement, the update method is overridden.
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
assert(widget ! =null);
assert(widget ! = newWidget);super.update(newWidget);
assert(widget == newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
Copy the code
The newWidget here is a new component configuration that was built when setState was created, so it is not the same as the oldWidget. For an InheritedWidget, it calls updated(oldWidget) first, which essentially notifishes the components that depend on InheirtedWidget to update:
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
/ / InheritedElement class
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget);
}
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.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); }}}Copy the code
You actually end up calling the didChangeDependencies method that relies on the InheritedWidget component to render elements. Let’s print it out here.The element ofdidChangeDependencies
Is called inmarkNeedsBuild
The element is marked as needing to be updated, and the rest of the process is completedStatefulElement
It’s the same. And for elements that have no dependent state, because there is no_dependent
, so it will not be updated. whileModelBinding
The component isStatelessWidget
So this first oneWidget
The configuration tree does not change once it is created, and the subcomponent tree can only change in two ways: 1StatefulWidget
Through thesetState
That’s not part of InheritedWidget, it’s done through StatefulWidget updates — which is not recommended, of course. 2. Does the subcomponent tree change depending on the state, then it will naturally update when the state changes.
With that, we finally understand how the InheritedWidget’s component tree is aware of and notifies the child component refresh process.
conclusion
The process of implementing component rendering in InheritedWidget is divided into the following steps:
- The mount phase binds the component tree runtime types to their corresponding InheritedElements and stores them in the _inheritedWidgets HashMap.
- When a child component adds a dependency on state, it actually binds the child component’s Element to an InheritedElement (the specific Element is taken from _inheritedWidgets). Is stored in the _dependents HashMap;
- When status updates, InheritedElement directly uses the old component configuration to notify the child Element that its dependencies have changed by calling the Element’s didChangeDependencies method.
- In Element’s didChangeDependencies, mark the Element as needing to be updated and wait for the next frame to refresh.
- Sub-components that do not have dependencies are not added to _dependent and therefore are not notified to refresh, thus improving performance.
There are several articles on the principle of state management, through which I hope to achieve the goal of knowing what it is and what it is. In fact, the core of Component rendering with Flutter is how state management is selected for component rendering, which has a significant impact on performance. Next, we’ll look at the application of the state management plug-in in a real-world example.
I’m an island user with the same name on wechat. This is a column on introduction and practice of Flutter.
👍🏻 : feel a harvest please point to encourage!
🌟 : Collect articles, convenient to read back!
💬 : Comment exchange, mutual progress!