The core class diagram

Introduce Flutter three trees

void main() { runApp( const MyApp() ); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( home: HelloWorldPage(), ); } } class HelloWorldPage extends StatelessWidget { const HelloWorldPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Text("123"), ); }}Copy the code

Ps: A tree with only one child does look a bit like a linked list

First, within the main function, we call the runApp method. Within the runApp method, the attachRootWidget method is called, which automatically generates the root nodes of the three trees

RenderObjectToWidgetAdapter is the root of the Widget tree node, the child is called runApp function, the incoming parameters

RenderObjectToWidgetElement Element is the root node of the tree

RenderObjectWithChildMixin RenderObject is the root node of the tree

 void attachRootWidget(Widget rootWidget) {
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    
    / / 1. New RenderObjectToWidgetAdapter, and call the attachToRenderTree
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner! , renderViewElementasRenderObjectToWidgetElement<RenderBox>?) ;if(isBootstrapFrame) { SchedulerBinding.instance! .ensureVisualUpdate(); } } RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {if (element == null) {
    / / 2. New RenderObjectToWidgetAdapter corresponding Element, Element is the root node of the treeelement = createElement(); }); owner.buildScope(element! , () {// 3. Build the Element treeelement! .mount(null.null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    returnelement! ; }Copy the code

The Widget tree

The widget is a logical tree, consistent with the widget structure in our build method

The Element tree

RenderObjectToWidgetElement in mount, can construct a complete Element tree

Through the child get RenderObjectToWidgetAdapter, corresponding Element to get its child nodes, let the mount operation on the node

If Element is of type ComponentElement

  1. inmountThe procedure will be calledElementthebuildThe method is herebuildMethod is called againWidgetorStatethebuildmethods
  2. mountIn the method, you getbuildmethodsWidgetReturn value, passWidgetThe return value gets the corresponding valueElementAnd then recursively executes againmountoperation

RenderObject tree

The RenderObject tree is a clipped version of an Element. If the Element is of Type RenderObjectElement, the RenderObject will be generated and attached to the RenderObject tree during mount

The reason for this design is:

The Widget is just a configuration object and the overhead of creating it is small

RenderObject is responsible for rendering/layout, etc., so we designed the Element tree. Element has an update mechanism. If the type is the same, we do not regenerate the Element, but update it with widgets. This reduces the number of renderObjects generated when the Widget is rebuilt

Nested

These are all part of the Nestd library, whose main purpose is to prevent too much nesting

Pseudocode comparison


// Nested:
MultiProvider(
  providers: [
    ChangeNotifierProviderA(),
    ChangeNotifierProviderB(),
  ],
  child: const MyApp(),
)


// flutter native:
MulitProvider(
    child: ChangeNotifierProviderA(
        child: ChangeNotifierProviderB(
            child: MyApp()
        )
    )
)
Copy the code

Element tree comparison

Nested:

Flutter native:

If you look at the middle line, you can see that the Element tree is basically the same, except for two __nestedhookElements, which are inherited from StatelessElement. There will be no RenderObject, so the RenderObject tree will be consistent and the display will not matter

Nested Element tree causes

InjectedChild and wrappedWidget pointing

Ps: In order to reduce the difficulty and focus on the core, the code listed is all streamlined code

 Nested({
    Key? key,
    required List<SingleChildWidget> children,
    Widget? child,
  })
Copy the code
class _NestedElement {
    Widget build() {
        var nextNode = widget._child;
        for (final child in widget._children.reversed) {
          nextNode = nestedHook = _NestedHook(
            owner: this,
            wrappedWidget: child,
            injectedChild: nextNode,
          );
        }
        returnnextNode; }}Copy the code

According to the build method of Element _NestedElement, a new widget type, _NestedHook, has been added to the widget tree. The corresponding Element is _NestedHookElement, and a new _NestedHook is created based on the inverted order of its children

Three attributes are saved in the i-th _NestedHook

The owner is always this

WrappedWidget is the i-th element of children

InjectedChild is

If I < children. Count - 1 return the I +1 _NestedHook else return the passed childCopy the code

At this point, we see why injectedChild and wrappedWidget are pointed to in the diagram

_parent pointing

See _NestedElement class declaration, as you can see with the SingleChildWidgetElementMixin

class _NestedElement extends StatelessElement with SingleChildWidgetElementMixin 
Copy the code

And SingleChildWidgetElementMixin implementation is such, rewrite the mount and the active, will determine whether the parent ELement in the process _NestedHookElement type, if it is, It points _parent to the parent ELement

mixin SingleChildWidgetElementMixin on Element { _NestedHookElement? _parent; @override void mount(Element? parent, dynamic newSlot) { if (parent is _NestedHookElement?) { _parent = parent; } super.mount(parent, newSlot); } @override void activate() { super.activate(); visitAncestorElements((parent) { if (parent is _NestedHookElement) { _parent = parent; } return false; }); }}Copy the code

So it’s easy to see why _parent is pointed to

The construction process of the Element tree

From the process of Element tree construction mentioned above, it can be concluded that when encountering ComponentElement, the build process is the most important part of generating subtrees

First look at the build method of _NestedHookElement

class _NestedHookElement {
 @override
 Widget build() {
    return wrappedChild!;
  }
}
Copy the code

Because out of the MulitProvider, all providers will inherit from SingleChildStatelessWidget, so we check SingleChildStatelessElement build method

class SingleChildStatelessElement { @override Widget build() { if (_parent ! = null) { return widget.buildWithChild(this, _parent! .injectedChild); } return super.build(); } } abstract class SingleChildStatelessWidget { Widget buildWithChild(BuildContext context, Widget? child); }Copy the code

From here you can see SingleChildStatelessElement invokes the widget buildWithChild, and will _parent! InjectedChild is passed as a parameter, and we follow the changeProvider inheritance chain to find the following code:

class InheritedProvider {
  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    return_InheritedProviderScope<T? >( owner:this, child: builder ! =null? Builder( builder: (context) => builder! (context, child), ) : child! ) ; }}Copy the code

And ChangeProvider itself does not implement createElement, so we follow the inheritance chain and find that createElement is available only when _InheritedProviderScope is available. The corresponding Element is _InheritedProviderScopeElement, again according to its inheritance chain – > InheritedElement – > ProxyElement, will find the following code

abstract class ProxyElement extends ComponentElement {
 @override
  Widget build() => widget.child;
}
Copy the code

Here, why ChangeProvider in the Element tree to corresponding into _InheritedProviderScopeElement, process and the structure of the tree clearly

Core principles

Primarily utilizing the InheritedWidget

fromInheritedWidgetRead the values

Because when Element is mounted, it calls _updateInheritance, where _updateInheritance

class Element {
  void _updateInheritance() {
    _inheritedWidgets = _parent?._inheritedWidgets;
  }
 }
Copy the code

However, when InheritedElement is used, the _updateInheritance method is overridden

class InheritedElement {

      @override
      void _updateInheritance() {
        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

When creating an Element tree, each Element has an _inheritedWidgets dictionary with a key that is the widget’s runtimeType and a value that is closest to the Element in the hierarchy. InheritedElement with the widget type runtimeType

Therefore, we can use Element’s _inheritedWidgets dictionary and the InheritedWidget type to find the closest InheritedElement we have. All Element tree nodes under InheritedElement can get data by taking the InheritedElement

Flutter has helped us to achieve good this method, getElementForInheritedWidgetOfExactType

class Element {
    @override
     InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
       final InheritedElement? ancestor = _inheritedWidgets == null ? null: _inheritedWidgets! [T];returnancestor; }}Copy the code

synchronousInheritedWidgetData changes of

If we update the InheritedWidget with a _dependentsmap () key, we update the InheritedWidget with a _dependentsmap key. To tell all elements that rely on this InheritedWidget to redo the build operation


class InheritedElement {
      @override
  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget)) {
          for (final Element dependent in _dependents.keys) {
          // Force the mark dirty, the next frame will be rebuilt
              dependent.markNeedsBuild()
          }
     }
   }
}
Copy the code

The rebuild call is in BuildOwner’s buildScope

BuildOwner {void buildScope(Element Context, [VoidCallback? Callback]) {dirtyElements[index].rebuild();  // A bunch of code}}Copy the code

So, as long as an Element causes itself to be included in the InheritedElement’s _dependents dictionary, it notifies itself when an InheritedElement changes.

While dependOnInheritedWidgetOfExactType can do this

class Element {
     @override
     T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
       final InheritedElement? ancestor = _inheritedWidgets == null ? null: _inheritedWidgets! [T];if(ancestor ! =null) { _dependencies ?? = HashSet<InheritedElement>(); _dependencies! .add(ancestor);// Core here
           ancestor._dependents[this] = null;
           return ancestor.widget;
       }
       _hadUnsatisfiedDependencies = true;
       return null; }}Copy the code

The core code

The difference and principle of Read/watch

Ps: To reduce complexity and highlight the core, the code is cleaned and trimmed

extension ReadContext on BuildContext {
    T read<T>() {
        return Provider.of<T>(this, listen: false); }}extension ReadContext on BuildContext {
    T watch<T>() {
        return Provider.of<T>(this); }}Copy the code
class Provider<T> extends InheritedProvider<T> {
     static T of<T>(BuildContext context, {bool listen = true{})// Find the Element in the Element tree where the Widget type closest to you is _InheritedProviderScope
      
     / / core:
        finalinheritedElement = context.getElementForInheritedWidgetOfExactType< _InheritedProviderScope<T? > > ()as_InheritedProviderScopeElement<T? >? ;if (listen) {
         // Core: add dependencies
         context.dependOnInheritedWidgetOfExactType<_InheritedProviderScope<T?>>();
        }
        finalvalue = inheritedElement? .value;return value asT; }}Copy the code

As you can see, read and watch the only difference is the listen is false or true, affect whether dependOnInheritedWidgetOfExactType, also said that if you use the watch, compared to read, If InheritedElement is updated, it triggers its own rebuild

Consumer and the Selector

The Consumer essentially uses provider. of, similar to the Watch

class Consumer {
@override
Widget buildWithChild(BuildContext context, Widget? child) {
  returnbuilder( context, Provider.of<T>(context), child, ); }}Copy the code

Selector is similar to Consumer, in that you have multiple caches, which gives you some performance improvement over Consumer

  1. Using the widget. The selector (context); Get the numbers that changed

  2. Call _shouldRebuild, the changed data, the original data, return true, rebuild, return false, and go to step 3

  3. Compare the changed data with the original data using DeepCollectionEquality().equals

class _Selector0State { @override Widget buildWithChild(BuildContext context, Widget? child) { final selected = widget.selector(context); final shouldInvalidateCache = oldWidget ! = widget || (widget._shouldRebuild ! = null && widget._shouldRebuild! (value as T, selected)) || (widget._shouldRebuild == null && ! const DeepCollectionEquality().equals(value, selected)); if (shouldInvalidateCache) { value = selected; oldWidget = widget; cache = widget.builder( context, selected, child, ); } return cache! ; }}}Copy the code

ChangeProvider

ChangeNotifierProvider inherits to ListenableProvider, and inherits to InheritedProvider

ListenableProvider uses its _startListening method as its startListening property. In this method, it adds a listener to its value. Listen callback notifyClients, notifyClients will according to the key in the map, to inform all depend on the InheritedWidget Element to perform the build operations

The ChangeNotifierProvider create attribute and startListening attribute are triggered when the value is first fetched. After that, if the value calls a notifyListeners, the listeners are triggered. Make dependencies fully rebuilt

class ListenableProvider { static VoidCallback _startListening( InheritedContext e, Listenable? value, ) { value? .addListener(e.markNeedsNotifyDependents); return () => value? .removeListener(e.markNeedsNotifyDependents); }}Copy the code

Related information:

Gityuan.com/2019/06/29/…

Gityuan.com/2019/06/15/…

www.liujunmin.com/flutter/pro…