preface

Recently, WHEN I was working on the Flutter project, I was thinking about why Flutter was written in such a nested way. Why not define a View as a property and add it to the parent View, as native development does?

With that in mind, I looked at the Widget’s source code (defined in Framework.dart) and found other issues along the way

  1. Widgets, Elemnet RenderObject the relationship between the three trees, as well as the construction of the three trees and update process
  2. Why is the setState method used to update the state
  3. How is GlobalKey implemented
  4. What is ParentData
  5. How do I pass data between widgets using inheritedWidgets

Let’s start with the Widget build and update process and answer some other questions as well

Tips:

  1. If you don’t want to look at the code, you can look directly at the flow chart at the end of the section below to see how the tree is built and updated
  2. If no mention of at the bottom of the tree is what tree, generally refers to the Widget, Elemnet, RenderObject three trees
  3. The following code block uses… Instead of assertions and debugging code, I’m going to omit it here because of space constraints, but it’s recommended to look at it in detail during development because it helps you understand debugging information

Concept Description

Widget, the State, Elemnet RenderObject, BuildOwner and BuildContext concepts

Widgets and the State

The configuration data used to describe the UI is used to describe Element. It is also a producer that generates Element and RenderObject.

Element

Elements are real nodes. He is also a manager, managing widgets,State nodes, and building and updating Element trees using widgets,State. It also manages the build and update of RenderObject trees

BuildOwner

BuildOwner is a dispatch center. Widgets are used to manage the build and update process of the Element tree, which in turn builds the Widget and RenderObject trees.

In the process of learning about Flutter, we are generally exposed to it. However, when we are developing, we usually only work with widgets, so how do we build three trees? BuildOwner is actually a scheduling center that executes the following process when it receives an external call

  1. Create an Element from a Widget. BuildOwner then binds the Element.
  2. During Element binding, Element calls the Widget’s build method or the Build method in State to generate a child Widget
  3. This child Widget produces a child Element. And unbind the child Element. When binding the child Element B, the parent of the child Element points to the Element in Step 2. During binding, step 2 is taken

By repeating step 2,3, you build the Element tree by creating the Element nodes from the top down. In this process, widgets and Elements are one-to-one. Each Widget generated corresponds to an Element.

RenderObject

Renderobjects are objects rendered on the screen, similar to UIView in iOS development. Holds a Layer for drawing.

When binding to an Element, if necessary, the widget is called to create a RenderObject, which Element holds. Note that not every Element has a RenderObject. Because not every element is rendered on the screen. Some elements simply describe the size and position of the RenderObject, and some elements store dependencies.

When building down the Element tree, if there is a child Element that holds the RenderObject, the parent property of the RenderObject in the child Element points to the RenderObject at the next level up. So the resulting tree might look something like the one below

The Widget tree corresponds to the Element tree, but the Element tree does not correspond to the RenderObject.

Several role relationships are as follows

To understand the build and update process, look at the process step by step from the source code

The construction process of two trees

The build process

Starting with the main function, the runApp() method is called and a widget is passed in. This runApp(Widget app) is implemented as follows

WidgetsFlutterBinding.ensureInitialized() .. scheduleAttachRootWidget(app) .. scheduleWarmUpFrame();Copy the code

We can see that scheduleAttachRootWidget is called with WidgetsFlutterBinding, and what that WidgetsFlutterBinding is we’ll talk about later, it’s not in the scope of this article, Take a look at the scheduleAttachRootWidget(Widget rootWidget) method, which calls attachRootWidget as shown below

 void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
  }

Copy the code

attachRootWidget(Widget RootWidget) is mainly used in the incoming rootWidget create a RenderObjectToWidgetAdapter object and call the RenderObjectToWidgetAdapter attachToRenderTree method of objects The attachToRenderTree method is implemented as follows

void updateRenderObject(BuildContext context, RenderObject renderObject) { } RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) { if (element == null) { owner.lockState(() { element = createElement(); assert(element ! = null); element.assignOwner(owner); }); owner.buildScope(element, () { element.mount(null, null); }); SchedulerBinding.instance.ensureVisualUpdate(); } else { element._newWidget = this; element.markNeedsBuild(); } return element; }Copy the code

If the incoming elemnt is empty, is to create a new RenderObjectToWidgetElement. If it is not empty, the markNeedsBuild method of Elemnt is called. The _ELEMnt object is normally null when calling runApp(MyApp()). Non-null is usually found when updating widgets, which is discussed in the next section. Let’s talk about the initial build

During the initial build process, the following method of owner is called

buildScope(Element context, [VoidCallback callback])
Copy the code

As mentioned earlier, BuildOwner is a scheduling center that calls Element to handle the build and update process of the tree. This is done in the buildScope method, which designs the update process to the tree and will be covered in the next section. Again, the call to the buildScope method above passes in a callback, as follows

     () {
        element.mount(null, null);
      }
Copy the code

This callback will be executed before the buildScope method does any specific processing, that is, element.mount(null, null) will be called before the buildScope method does any specific processing;

The mount method is used to bind an Elemnt to the Elemnt tree. So how does it bind? Take a look at the implementation of the mount method in the Element class

The mount method

void mount(Element parent, dynamic newSlot) {
    ...
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
   ...
  }
Copy the code

The mount method in the Element class does a few simple things

  1. Update the _parent value to the passed value
  2. Update _slot _depth _activie. _slot is a location marker, _depth is the depth of the Elemnt in the Elemnt tree, and _activit is the state of the Elemnt
  3. Update the owner of this Elemnt to the owner of the parent Elemnt. Because managing the tree requires only a BuildOwner, the owner property of the root Elemnt is given a BuildOwner, and the child Element uses only that value
  4. Register global Key to update dependencies

As for point 4, because it involves the update process, it will be covered in the third section.

For the first point, the parent passed in is assigned to the _parent attribute of Elemnt, indicating that the parent node of Elemnt is the corresponding Elemnt of parent. If parent is null, this is basically a root node.

As you can see from the mount method, the reference to the parent node is updated in the Elemnt class during the binding process, and each Elemnt knows who the parent node is. But what about the children of Elemnt? How do you build child nodes?

The RenderObjct tree has a parent for every Element except the root, so both Element and RenderObjct base classes have a parent attribute that specifies who the parent is.

But Element, RenderObjct tree not every node has child nodes, so in the design of can see, Widet, Element, RenderObjct the three medical foundation classes are no child nodes. Are left to their subclasses to implement.

Elemnt has two large subclasses, one is ComponentElement, which corresponds to Elemnt that is not rendered directly on the screen and is used to combine other elements, and the other is RenderObjctElemnt, which corresponds to Elemnt rendered on the screen.

Mount method of the ComponentElemnt class

In the ComponentElemnt class and its subclasses, the mount method is implemented as follows

void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _firstBuild();
  }
Copy the code

The rebuild() method is called from Element. The rebuild() method is called from Element

void rebuild() { assert(_debugLifecycleState ! = _ElementLifecycle.initial); if (! _active || ! _dirty) return; // If the status is inactive or dirty, return... performRebuild(); // Call the performRebuild method... }Copy the code

In the rebuild() method, check whether the status is active and not dirty. If not, no operation is performed. That’s ok. If so, the performRebuild() method is called, and performRebuild() in ComponentElemnt is implemented as follows

void performRebuild() { ... Widget built; try { ... built = build(); // Execute the build method... } catch (e, stack) { _debugDoingBuild = false; built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); },),); } finally { // We delay marking the element as clean until after calling build() so // that attempts to markNeedsBuild() _dirty = false during build() will be ignored. . _child = updateChild(_child, built, slot); _child = updateChild(_child, built, slot); . } catch (e, stack) { built = ErrorWidget.builder( _debugReportException( ErrorDescription('building $this'), e, stack, informationCollector: () sync* { yield DiagnosticsDebugCreator(DebugCreator(this)); },),); _child = updateChild(null, built, slot); }... }Copy the code

This code does two things. The first calls the build() method to get a Widget, and the second uses the Widget to generate an Elemnt and update the Element tree. ComponentElemnt class build() is an empty method, the specific implementation of the subclass to do, ComponentElemnt has three main subclasses, we commonly used class basic inherit these three classes. Let’s look at the implementation of the build() method in these three classes

  1. StatelessElement
Widget build() => widget.build(this);
Copy the code
  1. StatefulElement
Widget build() => _state.build(this);
Copy the code
  1. ProxyElement
 Widget build() => widget.child;
Copy the code

The StatelessElement and StatefulElement calls are the ones we dealt with directly in development

Widget build(BuildContext context);
Copy the code

For each of these classes, the purpose is to get a widget. Then this wiDET calls the updateChild method of Elemnt, which is the most important method in Framework.dart. Throughout the Element tree build and update process, the updateChild method is defined as follows

Element updateChild(Element child, Widget newWidget, Dynamic newSlot) {if (newWidget == null) {//1 Remove Element if (child! = null) deactivateChild(child); return null; } Element newChild; if (child ! = null) {//2 Update Element... If (hasSameSuperclass && child.widget == newWidget) {//2.1 has the same widget if (child.slot! = newSlot) updateSlotForChild(child, newSlot); NewChild = child; newChild = child; } else if (hasSameSuperclass && Widget.canupdate (child.widget, newWidget)) {//2.2 Widgets are not the same, but canUpdate does not return yes, Reuse elemnt, update Element's widget if (child.slot! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); . newChild = child; // add a new Element deactivateChild(Child) to an element deactivateChild(); . newChild = inflateWidget(newWidget, newSlot); }} else {//3 Create or reuse an Element from GloabalKey // Return a new Element based on the value of newWidget newChild = inflateWidget newSlot); }... return newChild; }Copy the code

Because a lot of the logic for updating the widget tree is designed here, we’ll come back to this approach when we talk about the update process. When we first built, we created the widget, but we didn’t create the Element yet, so the widget is not null and the child is null. This will go directly to the inflateWidget(newWidget, newSlot); In this method. An inflateWidget is defined as follows

Element inflateWidget(Widget newWidget, dynamic newSlot) { ... final Key key = newWidget.key; if (key is GlobalKey) { final Element newChild = _retakeInactiveElement(key, newWidget); if (newChild ! = null) {// If a GlobalKey exists and is already included in inactive Elements, it will be reused, otherwise a new one will be created... newChild._activateWithParent(this, newSlot); final Element updatedChild = updateChild(newChild, newWidget, newSlot); assert(newChild == updatedChild); return updatedChild; } } final Element newChild = newWidget.createElement(); // Create a new element... newChild.mount(this, newSlot); // Mount the new element... return newChild; }Copy the code

In this method, you first process the GloabalKey logic (described in Section 3) and then call the Widget’s createElement() method to create an Elemnt. After the Element method is created, the mount() method is called on that Element.

The Elemnt mount method is called again, but note that this is the child Element’s mount method. When the mount() method of the child Element is called, the binding process is repeated until there are no children. The call flow is as follows

mount -> firstBuild -> rebuild - > performRebuild -> updateChild -> inflateWidget -> mount
Copy the code

If you see this and think it’s a bit complicated to invoke the flow, you can forget about the flow and see the build flow diagram later

Mount method of RenderObjctElemnt

In RenderObjctElemnt, the mount method is defined as follows

@override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); . attachRenderObject(newSlot); // bind render object _dirty = false; }Copy the code

The RenderObjctElemnt attachRenderObject method is called to attach a RenderObjcet to the RenderObjctElemnt AttachRenderObject in RenderObjctElemnt is implemented as follows

@override void attachRenderObject(dynamic newSlot) { _slot = newSlot; _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement? .insertChildRenderObject( renderObject, newSlot); final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement(); if (parentDataElement ! = null) _updateParentData(parentDataElement.widget); }Copy the code

AttachRenderObject method, called first _findAncestorRenderObjectElement find is RenderObjctElement type Element. Assigned to _ancestorRenderObjectElement. Call again insertChildRenderObject inserted the corresponding RenderObjct to _ancestorRenderObjectElement child RenderObelct list. This is done because the RenderObjct tree is not one-to-one with the Element tree. RenderObjct will render nodes on the screen, not all elements will render on the screen. So, in order to ensure correct renderObjct tree building, you need to call _findAncestorRenderObjectElement find renderObjct superior Element node, ignore the node has nothing to do with the rendering, find the parent RenderObject.

The mount method in RenderObjctElemnt only calls attachRenderObject to bind RenderObjct generated by the widget, but does not handle the child Element node. So RenderObjctElemnt, how do you build a subtree?

RenderObjctElemnt is a render node. However, not every rendered node will have child nodes, so the construction of child nodes is left to the concrete child node subclass to implement. RenderObjctElemnt has two subclasses have child nodes, respectively is a child node SingleChildRenderObjectElement MultiChildRenderObjectElement and more child nodes.

SingleChildRenderObjectElement mount method is as follows (child) in which the child is to create a widget

  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
Copy the code

MultiChildRenderObjectElement mount method is as follows. (one of the children is to create the widget childrenl list)

void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _children = List<Element>(widget.children.length); Element previousChild; for (int i = 0; i < _children.length; i += 1) { final Element newChild = inflateWidget( widget.children[i], IndexedSlot<Element>(i, previousChild)); _children[i] = newChild; previousChild = newChild; }}Copy the code

You can see from the code above that the updateChild calls the inflateWidget method. So for subclasses of RederObjctElement with child nodes, the inflateWidget method is called during the build process. The inflateWidget method generates a child node and calls the mount method of the child node. The process is shown below.

Here’s a summary of the tree building process

When the runAPP() method is called, call root Widge’s createElement method to get a root Element, call root Elemnt’s mount method, and continue to create child widgets. Call the createElement method of the child Widget to create the child Elemnt, call the mount method of the child Elemnt, and continue to create nodes until there are no leaf nodes for the child. The following figure

The renewal process of three trees

Update process

After building a tree, how to add, delete, and modify nodes in the tree? As mentioned earlier, BuildOwner is the manager that handles the build, updates the tree. Its role with the tree is roughly as shown.

The process for updating the tree is as follows: BuildOwner is responsible for a scheduling function and notifies BuildOwner that updates are needed when we operate the tree. BuildOwner’s buildScope method is called when the framework knows it needs an update. The Elemnt that needs to be updated is updated in the buildScope method.

Let’s start with the most familiar method, the setState method for State. The setState method is defined as follows

void setState(VoidCallback fn) { ... final dynamic result = fn() as dynamic; . _element.markNeedsBuild(); }Copy the code

The markNeedsBuild() method of _Element in State is called. The markNeedsBuild() method marks an Element as needing an update. Methods are defined as follows

void markNeedsBuild() { ... if (! _active) return; . _dirty = true; owner.scheduleBuildFor(this); // Add the build owner to the rebuild plan.Copy the code

The markNeedsBuild method first checks whether Elemnt is _active. If not, no processing is done because an inactive state is not displayed on the screen, so no processing is required. Then set its _dirty value to true to indicate that the Element needs to be updated. Then call BuildOwner’s scheduleBuildFor method and pass in the Element that needs to be updated. BuildOwner’s scheduleBuildFor is defined as follows

void scheduleBuildFor(Element element) { ... if (! _scheduledFlushDirtyElements && onBuildScheduled ! = null) { _scheduledFlushDirtyElements = true; onBuildScheduled(); // Notify Engine that the next frame needs to be updated; } _dirtyElements.add(element); . }Copy the code

Methods in _scheduledFlushDirtyElements indicate whether it is in the process of updating, you can see, here first to determine whether _scheduledFlushDirtyElements is true, is not in the update process, Call onBuildScheduled to notify the framework that it needs to update the tree. Then add the element you just passed in to BuildOwner’s _dirtyElements. The _dirtyElements is a list of Elemnt that needs to be updated.

When the framework receives the message that it needs to update the tree, it calls BuildOwner’s buildScope() method, which was mentioned earlier in the build process, but not in detail, so let’s look at the buildScope method implementation here

void buildScope(Element context, [VoidCallback callback]) { if (callback == null && _dirtyElements.isEmpty) return; . / / deng question what is the timeline timeline. StartSync (' Build ', the arguments: timelineArgumentsIndicatingLandmarkEvent); try { _scheduledFlushDirtyElements = true; if (callback ! = null) { ... try { callback(); //callback first executes} finally {... }}... try { _dirtyElements[index].rebuild(); // Rebuild} catch (e, stack) {... }... }... } finally { ... _dirtyElements.clear(); . }... }Copy the code

The buildScope() method starts with the callback method, which, for the first time built, is built by calling the mount method of root Elemnt.

The main thing this method does then is fetch each Element that needs to be updated from the _dirtyElements list, and then call the rebuild() method on Elemnt to clear the _dirtyElements list, marking the completion of the update.

As you know from the previous build process, the rebuild method calls the performRebuild method. PerformRebuild has a different additional implementation for different Element subclasses.

First, for RenderObjctElemnt, only updateRenderObject is called to update the RenderObjct object, and no child nodes are updated. So here’s the question: How do I update the RenderObjctElemnt subtree that has children? Here to leave a small question, such as finished ComponetElemnt and then answer.

For ComponetElemnt and its subclasses, calling the performRebuild method calls the updateChild method. The performRebuild and updateChild methods were pasted in the build process, so I don’t have to paste the code again. I’ll focus on the updateChild method here

updateChild

Three parameters (Element Child, Widget newWidget, and Dynamic newSlot) are passed in to updateChild. Child represents the child node of the ComponetElemnt, newWidget is the Widget corresponding to the child node, and newSlot is the location information of the child node

The following is done during updateChild execution based on the parameters passed in.

  1. If the new widget is empty, but the Element is not empty (that is, the original widget is removed). First deactivateChild (child), if the child is not empty, unbind the child’s renderObjce, add it to the _inactiveElements list in the Build Owner, and return the function. DeactivateChild (child adds child to BuildOwner_inactiveElements)

  2. If the new widget is not empty, the child is not empty

  • 2.1 If the widget passed in is the same as the original widget but the slot is different, updateSlotForChild is called to update the location

  • 2.2 Check whether the widget’s canUpdate is true if it is not the same widget. If it is true (indicating that the element is reusable), check whether the slot is equal to the original slot. UpdateSlotForChild is called to update the location. The update method of Elemnt is then called.

  • If not, call deactivateChild(Child). Call inflateWidget(newWidget, newSlot) to generate a new child

  1. NewWidget is empty and Child is empty. Indicates that the Wiget does not yet have an Element, and calls the inflateWidget to create a new Element.

The above 1 and 2 respectively represent the three cases of deleting, modifying and adding the child node of Elemnt. When the markNeedsBuild method is called on an Element (an instance of ComponetElemnt and its subclasses), the updateChild method is called to update the element.

For Step 1, when a Widget is no longer in use, the deactivateChild method is called, which puts the corresponding Element into the BuildlOwner _inactiveElements list, If Element is used again (such as with GlobalKey), it is removed from the _inactiveElements list. If it is not used again, it will be destroyed the next time the tree is updated.

For Step 2.2, the Elemnt update method simply sets the widget. The specific implementation is implemented by each subclass. This step allows the buID method to update the tree down

For example, StatelessElement calls rebuild and RenderObjectElemnt updates RenderObjct. Such as SingleChildRenderObject and MutilChildRederObjectElemnt son elemnt will call updateChild method (MutilChildRederObjectElemnt UpdateChildren is called, but updateChildren is a wrapper around updateChild, and updateChild is called one by one on each list passed in.

Update process is shown in the following figure

RenderObjctElemnt: Update the subtree of RenderObjcetElement that has children.

RenderObjcetElement’s markNeedsBuild method is not called directly during development. Take the Wigdet of Stack. Stack inherited from MultiChildRenderObjectWidget. MultiChildRenderObjectWidget corresponding MutilChildRederObjectElemnt.

But you’ll notice that Stack has no setState method, which means it doesn’t call the markNeedsBuild method directly. A Stack is typically wrapped in widgets (such as StatefuleWidget) for ComponetElemnt and its subclasses. The markNeedsBuild method is called when the corresponding Widget is updated. Enter updateChild to update the subtree.

Suppose you want to update process, retain the Stack, only delete a child node in the Stack, go to step 2.2 updateChild, call Stack corresponding MutilChildRederObjectElemnt update () method. UtilChildRederObjectElemnt update () as follows

@override void update(MultiChildRenderObjectWidget newWidget) { super.update(newWidget); . _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren); _forgottenChildren.clear(); }}Copy the code

As you can see, the update method calls the updateChildren method, which iterates through the child nodes, calling the updateChild method on each child node to update the subtree.

To mark an Element that needs to be updated, call markNeedsRebuild to mark the Element that needs to be updated and inform the Flutter framework that it needs to be more detailed. When the framework updates the Element, it calls the updateChildren method to recursively update the subtree down to the leaf node.

Implementation principles of GlobalKey,ParentData, dependency update and other mechanisms

GloabalKey

During development, one Widget can find another Widget by GloabalKey. How does this work?

When we call GlobalKey’s currentWidget to get a Widget, we call the following method

Widget get currentWidget => _currentElement? .widget;Copy the code

The _currentElement method is defined as follows

Element get _currentElement => _registry[this];
Copy the code

But what is _registry[this]?

If you look at the GlobalKey definition, you can see that the class in GlobalKey has a class attribute

static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
Copy the code

The _registry takes GlobalKey as the key and Element as the value. It stores the Widget’s key as an Element of the GloabalKey

The mount method is called when an Element is bound. The mount method also checks whether the widget key is GlobalKey or GlobakKey

key._register(this);
Copy the code

_register is a method in GlobakKey

void _register(Element element) {
    _registry[this] = element;
  }
Copy the code

This method stores the GloabakKey and Element as key pairs in _Registry in the GlobalKey class. This completes a GlobalKey and Element binding

_registry[this] retrieves the Element of the GlobakKey from _registry in the GlobakKey class

In this way, when we call currentWidget for GlobalKey to retrieve a Widget, we are actually fetching Element from the _registry in the GlobalKey class and returning Element’s Widget.

As long as the Element corresponding to the GlobalKey exists, the Widget can be obtained.

The process is roughly as follows

ParentData

In the RenderObjct tree, the parent RenerObjct is responsible for the layout of the child RenderObjct. Each RenderObjct has a parentData property that provides layout information to the parent RenderObjct. The parent RenderObject is laid out according to the parentData of the child RenderObjct. But when was parentData provided?

RenderObecjElement usually does not have a child node of type RenderObecjElement. Usually, two RenderObecjElements will have a middle node of type ParentDataElemnt. This is because we need to set the parentData attribute to the child RenderObecjElement, where RenderObjct’s parentData information is set.

Take the Stack column as an example. The Stack usually doesn’t have a directly nested Elemment is a child Widget of the RenderObjctElementl type. Usually a Position is nested. Positio derives from ParentDataWidget, and the Element corresponding to ParentDataWidget is of type ParentDataElemnt. The Widget tree built with Stack and Position corresponds to the Element tree shown in the figure below.

If Widget D’s Element is of type RenderObjctElement, attachRenderObject is called when the Widget’s Element is attached to the tree. The implementation of the attachRenderObject method was posted earlier. RenderObjct = RenderObjct = RenderObjct = RenderObjct = RenderObjct

final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement(); if (parentDataElement ! = null) _updateParentData(parentDataElement.widget);Copy the code

If there is a parent Element of type ParentDataElement, then the _updateParentData method is called and widget._updateparentData of ParentDataElement is passed as follows

void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) { bool applyParentData = true; . if (applyParentData) parentDataWidget.applyParentData(renderObject); }Copy the code

One thing this _updateParentData does is call the applyParentData method of the widget passed in to Elementd and pass in Elementd’s renderObjct.

Going back to the previous example, when binding Element D of Widget D, Element D looks up to get ParentDataElement B, Find the corresponding Positon B by ParentDataElement B and call the applyParentData method of Position B. The applyParentData method in Position is as follows

@override void applyParentData(RenderObject renderObject) { assert(renderObject.parentData is StackParentData); final StackParentData parentData = renderObject.parentData as StackParentData; bool needsLayout = false; if (parentData.left ! = left) { parentData.left = left; needsLayout = true; } if (parentData.top ! = top) { parentData.top = top; needsLayout = true; } if (parentData.right ! = right) { parentData.right = right; needsLayout = true; } if (parentData.bottom ! = bottom) { parentData.bottom = bottom; needsLayout = true; } if (parentData.width ! = width) { parentData.width = width; needsLayout = true; } if (parentData.height ! = height) { parentData.height = height; needsLayout = true; } if (needsLayout) { final AbstractNode targetParent = renderObject.parent; if (targetParent is RenderObject) targetParent.markNeedsLayout(); }}Copy the code

In this method, it first takes the parentData of renderObject(in this case, Element D’s renderObjcet) and updates the parentData based on Positon B to determine whether it needs to be updated. If it needs to be updated, Update the RenderObjct by calling the parent of the renderObject and telling the parent that it needs to be rearranged

The process is as follows

Find the parent ParentDataElement -> call the applyParentData method of the widget of ParentDataElement -> Set parentData -> notify the parent RenderObjct of updatesCopy the code

The Element tree and the RenderObjct tree are closely related, and the RenderObjct tree changes as the Element tree is built and updated.

Although only Stack and Position are taken as examples, RenderObjct tree nodes basically rely on this mechanism to achieve layout information transfer.

Dependencies and updates

During development, you can use methods like theme.of (context) to get theme-related Settings. When we use the theme information and the theme information changes, our Widget also re-executes the Build method. This depends on building and updating the Element tree. Look at the implementation of the InheritedElement. Leave a hole for this point and analyze it together when analyzing the Provider.

conclusion

From the above Element build and update process, we can see that the nodes are modified level by level through recursive calls. The Flutter code writes widgets in a nested manner to ensure that each Widget is up to date, thus ensuring that the Element tree is correct.

One thing to note is that every Element and RenderParent has a parent attribute that identifies the parent node, but widgets don’t have this parent node, meaning widgets have no parent relationship. So, strictly speaking, the Widget tree is not really a tree of nodes, only that there is a Widget for each Element, and the Widget looks like a tree from the development process.

Element is the core, and the entire build and update process is triggered by BuildOwner’s buildScope method, implemented by Element, which coordinates the build and update of the Widget tree and RenderObjct tree.

conclusion

In this article mainly tells the story of the Widget, Elemnet, RenderObjct tree build and update process, as for the few RenderObjct implementation description. RenderObjct layout, rendering, layer generation and composition is also an important part of the understanding of Flutter interface construction. It will be discussed in the next article