preface
Flutter Widget is a modern, responsive framework that uses widgets to build UI. Weiget describes UI configuration information. When the widget’s state is sent to change, the widget redraws the UI and the Flutter compares the changes to determine the minimum changes needed to transition the underlying rendering tree from one state to the next. This minimal change is the update mechanism we will talk about today.
Element network
The three trees of Flutter are important parts of the framework layer. The Widget tree describes the CONFIGURATION information of the UI, the Element tree connects the configuration information to the UI rendering, and the RenderObject is responsible for the actual UI rendering based on the configuration information.
Knowing the function of Element, let’s take a look at the structure of Element 👇 :From the picture above we can know:
- Element holds a reference to the Widget
ElementYou can compare the references to the widgets you hold to see if they have changed and get information about the widgetsCopy the code
- Element implements the BuildContext interface
As long as getElementYou can get the Size informationCopy the code
- Elements can be divided into two types: composite and rendered
Composite features: There is only one child node, and the content of the child node is the content of the Widget's build method. The content of the child node of StatelessElement, such as the Widget returned by the StatelessWidget's build method. Rendering features: Hold rendering object RenderObject, directly involved in the generation of rendering treeCopy the code
Now that you know the basics of Element, let’s take a look at its life cycle.
Element life cycle
Element’s declaration cycle is as follows:Let’s look at what each node does in detail.
InflateWidget method
The parent node calls this method to initialize the child Widget and create the child Element object corresponding to the child Widget. This method is called when the parent node is initialized or updated.
CreateElement method method
Create the Element object corresponding to the Widget and use the Widget as the UI configuration for the constructed Element, which has already been constructed, in the inflateWidget method.
The mount method
The framework calls the mount method of an Element to add the constructed Element to the Element tree, and calls the inflateWidget method of the child node to construct and mount the child node. The rendered Element also constructs and binds the RenderObject at this point. After the mount method, the constructed Element with an active state is displayed on the screen.
update
When an Element that is already displayed on the screen needs to be updated by its parent, after the update method on the parent is triggered, the framework compares the key and runtimeType of the Widget that updated the parent. If they are equal, It calls Element’s Update method to update itself.
The destruction
The destruction of elements can be divided into active destruction and passive destruction. Active destruction means that the ancestor node can actively call the deactivateChild method to destroy the child node. Passive destruction is when the Widget’s key is not the same as the runtimeType when the parent updates the Widget and the Element is destroyed.
From the above lifecycle analysis, we can see that the operation of an Element update is to call its own UPDATE method. Different types of elements have different update processes, 👇 let’s first analyze the specific update mechanism for multi-node and single-node.
MultiChildRenderObjectElement update
MultiChildRenderObjectElement Element node type is more, it is update to update their all child nodes, all the processing updateChildren method, the process is as follows:
- Diff from top to bottom and update child nodes
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Widget newWidget = newWidgets[newChildrenTop];
/// The basis of a diff
if (oldChild == null| |! Widget.canUpdate(oldChild.widget, newWidget))break;
/// Update operation
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
// This is Slot
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
Copy the code
Diff is the key and runtimeType of the new and old widget. If the two are not equal, there is no need to update it and it just jumps out of the loop. For example, if you want to display Text instead of Image, you don’t need to reuse Element, so you jump out of the loop. If the two are equal, then reuse is possible, so try updating Element. For example, if you want to display Text(“s”) instead of Text(” A “), just update Element’s widget reference. The execution process is as follows:
- Bottom-up diff
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
final Widget newWidget = newWidgets[newChildrenBottom];
/// diff
if (oldChild == null| |! Widget.canUpdate(oldChild.widget, newWidget))break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
Copy the code
Diff is the key and runtimeType of the new and old widgets, and if they are not equal, the loop is broken. The goal is to reuse as much as possible. The framework divides the list of child nodes into three parts: header, middle, and bottom. Once the header is updated, this step completes the division of the three parts. The execution process is as follows:
- Store reusable elements
So what elements can be reused? The widget.canupdate result is true.
Widget.canUpdate compares keys and runtimeTypes. The key exists as an identifier, and the framework does not reuse different keys. There is no need to reuse runtimetypes. Text and Padding are runtimetypes.Copy the code
Step one, you’ve updated the old and new lists from the top down widget.canand updated the reusable elements. In step two, the widget. canUpdate the old and new lists has been bottom-up.
The purpose of this step is to find reusable elements in the middle of the old list and store them. Then, when the new Widget list generates elements, you can see if there are any that can be reused. When updates occur, the first choice for Flutter is to reuse elements as much as possible, rather than create them.
The code is as follows:
/// Save the Key Element
final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
/// The value can be: Key: Key value: Element of the Widget
/// When fetching an Element, you can retrieve it directly from the map using the key
Map<Key, Element> oldKeyedChildren;
/// If there is an old list
if (haveOldChildren) {
oldKeyedChildren = <Key, Element> {};while (oldChildrenTop <= oldChildrenBottom) {
final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if(oldChild ! =null) {
if(oldChild.widget.key ! =null)
/// Here we save Element (1) with the Key
oldKeyedChildren[oldChild.widget.key] = oldChild;
else
deactivateChild(oldChild);
}
/// Update the index
oldChildrenTop += 1; }}Copy the code
The core is the processing at (1), oldChild is the old Element, Element holds the Widget reference, so we can get what Element’s Widget Key is. Save Element if there is a Key. The execution process is as follows:
- Update the middle Element
Now the list of old elements has been scanned and the reusable elements have been saved. Updates can then be done sequentially, generating or reusing elements based on the list of newly passed widgets.
Why not update the bottom of the scan?
Because Slot information is not available, Slot is the difference between multiple nodes and single nodes. A multi-node Element assigns a Slot to each child node, which we can think of as location information, where it is in the multi-node. The Slot of each Element is a reference to the previous node. PreviousChild in the updateChild(oldChild, newWidget, previousChild) method is the Slot information of this node. Only when the execution is top-down does it record what the previous node was. So previousChild = newChild
The core code is as follows:
// Update the middle.
while (newChildrenTop <= newChildrenBottom) {
Element oldChild;
final Widget newWidget = newWidgets[newChildrenTop];
final Key key = newWidget.key;
///Retrieves the saved Element, or null if none exists
oldChild = oldKeyedChildren[key];
///If null, one will be generated based on the newWidget
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
Copy the code
Note here ⚠️: anchor oldChild is saved if an Element can be retrieved. If it is not fetched, oldChild is null, indicating that there is no reusable one.
The execution process is as follows:
Now that the head and middle are generated or updated, it’s time for the last part, the bottom processing.
- Update the Element in the bottom section
The bottom processing is similar to the middle processing, and will be fetched from the Map first, if no new ones are generated. I won’t go into the details here.
// Update bottom.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = oldChildren[oldChildrenTop];
final Widget newWidget = newWidgets[newChildrenTop];
final Element newChild = updateChild(oldChild, newWidget, previousChild);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
Copy the code
Multi-node update summary
- Multi-node updates are updates to their own child nodes.
- There are two ways to update the framework: to reconstruct an Element or to reuse it, using the widget.canupdate method.
- Multi-node updates are divided into top-down diff and update, bottom-up DIff scan, saving reusable elements from the old list, progressively updating the middle part, progressively updating the bottom part.
Update of a single node
Above we looked at multi-node updates: update each child node. So how does the child node get updated. Let’s talk about the update process of a node. Child node updates are still the update method.
The question is, when can Element be reused?
If the widget.canupdate of the old and new Widget returns true, it can be reused.
In both cases, it is possible to reuse Element, and in the rest it is not necessary. 👇 Let’s look at the update method.
update
Method input parameter:
Element child: The Element that you want to update, which can be null. Widget newWidget: New UI dynamic newSlot: Slot information that you want to display, changed to NULL if the parent node is a single node. If the parent node is not a single node, the change value is the previous node.Copy the code
Method return value:
Null: the content does not need to be displayed. Child: The information (such as slot points and UI configuration) of the child is reused. New: the information cannot be reused and is directly constructedCopy the code
The core code is as follows:
Element updateChild(Element Child, Widget newWidget, dynamic newSlot) {if (newWidget == null) {return null; } if (child ! ③ if (child.widget == newWidget) {if (child.slot! = newSlot) updateSlotForChild(child, newSlot); return child; } if (Widget.canUpdate(child.widget, newWidget)) { if (child.slot ! = newSlot) updateSlotForChild(child, newSlot); // Key can be reused as runtimeType ④ child.update(newWidget); return child; } deactivateChild(child); } // re-construct ② ⑤ return inflateWidget(newWidget, newSlot); }Copy the code
First code: If the widget to display is NULL, there is no content to display, and you can return NULL directly
Second piece of code: If child is null, then there is nothing to reuse and elements can be constructed directly, such as during initialization or multi-node updates.
Third piece of code: The old and new Widdget is a Widget, so you can reuse Element, but we see that slot information is being compared here. For example, the original Element UI is Text(“xihongshi”). The desired UI is also Text(“xihongshi”), but the original Element was displayed in the first position. Now the Element is displayed in the second position. That’s the difference between the slots. In this case, Element displays the same content but updates the slot.
Code # 4: The old and new widgets have a canUpdate value of true, which means they can be reused by simply updating the displayed content.
The canUpdate value of the old and new widgets is false and cannot be reused.
Update judgment form
summary
- Reuse standards when updating children
Whether the values of the new and old widgets are equal Whether the Key and RuntimeType of the new and old widgets are equalCopy the code
- How does it help us develop
When there is inconsistency between the data and the UI, we need to rule out the reuse of Element.Copy the code
Knowing about single-node and multi-node updates, let’s use a few small cases to add an understanding of the update mechanism.
case
Before going into the specific example, let’s clarify the concept that a Widget is the UI to display, and an Element is the actual UI to display based on the Widget. The code for the following cases is in the Flutter update.
Case a
The code example is in the SwapColorDemo1 class. But when we click the button, the color blocks switch.
From the code, the widgets are exchanged. Phenomenally, the blocks are swapped.
👆 : The Widget is the UI to display, and the Element is to display the UI according to the Widget. Since our colors are in the Widget, there are several situations where block swapping can be done:
Element’s widgets point to new widgets, such as the yellow widget that Element pointed to instead of the red widget. Based on the swapped widgets, the Elements are regenerated. For example, destroy the Element that originally hosted the red Widget and generate an Element based on the yellow Widget. Swap elements directly. For example, swap the red and yellow Element positions
Let’s see that our code falls into that category.
When we click will trigger the build method, will trigger a Row control updates, Row corresponding Element is MultiChildRenderObjectElement, its update is executed to the upper we speak updateChildren method.
Our Widget looks like this
So in the updateChildren method, we just stay in the first step of the loop —– diff down and update the child nodes
Because widget. canUpdate returns true. Because the runtimeType of the old and new widgets is StatelessColorfulTile, and because we did not set the Key manually, the Key property of the old and new widgets is null. So canUpdate returns true. The framework thinks it’s ready to reuse, so it updates the child.
👇 let’s see how to update child. Input case: Child is the old Element newWidgets is the widget to display, here is our custom widget. Solt has not changed the reuse update logic performed by the update judgment table above.
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { ... // Key can be reused as runtimeType ④ child.update(newWidget); . }Copy the code
It’s executing the logic of the fourth code. Child is the Element that carries the StatelessColorfulTile, and StatelessColorfulTile is the StatelessWidget, so child is StatelessElement.
The call process is as follows:
The displayed UI is the UI returned by the Build method of the Widget object that Element holds. We store the color information in the Widget, so we swap colors normally. The effect is as follows:
The bottom block represents Element and the text represents Widget. The Element does not change, only the Widget it points to is changed, which is why the color swap occurs.
summary
StatelessWidget color replacement reasons:
- Reuse Element and update the Widget that Element points to
- Widgets are stateless widgets in which colors are stored.
Case 2
The code example is in the SwapColorDemo2 class. But when we click the button, the color block doesn’t change.
From the code, the Widget does swap. Phenomenally, the blocks are swapped. Let’s take a look at why the color does not change after switching from a StatelessWidget to a StatefulWidget.
When we click will trigger the build method, will trigger a Row control updates, Row corresponding Element is MultiChildRenderObjectElement, its update is executed to the upper we speak updateChildren method.
Our Widget looks like this
So in the updateChildren method, we again just stay in the first step of the loop —– diff down and update the child nodes
Because widget. canUpdate returns true. Because the runtimeType of the old and new widgets is StatefulColorfulTile, and because we did not set the Key manually, the Key property of the old and new widgets is NULL. So canUpdate returns true. The framework thinks it’s ready to reuse, so it updates the child.
In case 1, the updateChild method also executes to code 4, but the Element in the widget is a StatefulElement. The call flow is as follows:
Unlike StatelessElement in case 1, the UI generated by Element is the build method of the State object, and our colors are stored in the State, which is initialized when Element is initialized. The following code:
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
...
super(widget)
}
Copy the code
In other words, Element and State objects live and die together. Element is reused, so Element’s State reference is constant, and our color is stored in the State object. As a result, Element simply replaces the Widget, leaving the color unchanged. The update effect is as follows:
summary
Reasons for not replacing StatefulWidget colors:
- Reuse Element and update the Widget that Element points to
- The State application held by Element does not change, and the color is stored in the State object
Case 3
The StatefulWidget is set with the Key, and the code case is in the SwapColorDemo3 class. But when we click the button, the color blocks can actually be swapped.
From the code, the Widget does swap. Phenomenally, the blocks are swapped. Compared with case 2, only the Key is added. Let’s take a look at why the color changes normally after the StatefulWidget adds a key.
Our Widget looks like this
Unlike cases 1 and 2, in the updateChildren method, we will first go to store reusable elements.
Step 1 and step 2 are skipped because the key is different each time the Widget is diff. For example, the key of the Element Widget in the first position is key1, and the key of the Widget to be displayed is key2
Here’s what the Map looks like after step 3:
Key1 :Element1 (Widget is 1) Key2 :Element2 (Widget is 2)Copy the code
After the third step is processed, start the gradual update. Now, where Widget1 was displayed, Widget2 is displayed. Follow the process: Widget2’s key is key2, find key2 in the Map, fetch Element2. But Element2’s slot is null because the previous node of Element2 was Element1, and now Element2 is displayed in the first location.
Final Element newChild = updateChild(oldChild, newWidget, previousChild); OldChild: is Element2, and the widget of Element2 is Widget2 newWidget: is Widget2 previousChild: Null -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- oldChild: Is Element1, and the widget of Element1 is Widget1 newWidget: is Widget1 previousChild: Element2Copy the code
The updateChild method is also executed in the third part of the updateChild code, but the new and old widgets are installed in the same place, but the slot information is different, so the location of the Element is swapped and the color is swapped. Because State swaps. The update effect is as follows:
summary
The StatefulWidget color is changed after the Key is set:
- Element is reused, and its Widget and State information are retained
- Updated Element location information
Four cases
We wrap the StatefulWidget with a Padding, and the code case is in the SwapColorDemo4 class. But when we click the button, the color block can appear randomly.
From the code, the Widget does swap. In view of the phenomenon, the graph blocks are randomly exchanged. Compared to case three, it’s just a layer. After the previous analysis, let’s first consider the question, under what circumstances do colors appear randomly?
We know that the color is in the State, so the Element that bears the State has been recreated. So when will it be recreated? When it can’t be reused. Next, let’s look at the survival of the Element in the StatefulWidget.
When you’re dealing with the Padding layer, the process is similar to case 1. It just stays in the first step of the loop —– diff down and updates the child nodes
Because widget. canUpdate returns true. The Padding has no Key because the runtimeType of the old and new widgets is Padding, and because we did not set the Key manually, the Key property of the old and new widgets is null. So canUpdate returns true. The framework thinks it’s ready to reuse, so it updates the child.
final Element newChild = updateChild(oldChild, newWidget, previousChild); The oldChild is an Element of the old Padding and the newWidget is the new PaddingCopy the code
Depending on the input, the fourth code of updateChild is executed. That’s the Padding update method, so I don’t need to go into that. When displaying the Padding child, the Key of the child is not the same as that of the fifth updateChild, so the child Element of the Padding Element is reconstructed, so the color State is also reconstructed.
The update effect is as follows:
summary
The StatefulWidget color appears randomly after wrapping the Padding:
- The Padding Element is reused, but the Padding child is not reused because the Padding child has a Key.
conclusion
We have a basic understanding of Flutter updating mechanism, and introduced its judgment and execution process in detail through specific cases. Tomato believes you have a better understanding of the update of Flutter. Do you know what happens if you add a key to the Padding? Welcome to leave a message ~ ~ ~