introduce

The most commonly used widgets in Flutter are the StatelessWidget and StatefulWidget, which correspond to stateless and stateful components, respectively. The state update method in StatefulWidget is setState(fn). After calling this method, the StatefulWidget’s Build method will be called again to rebuild the component and refresh the interface. After calling setState, what is the process for getting to build? With this in mind, analyze the update process for StatefulWidget by reading the source code.

The source code parsing

The setState method takes a fn parameter, and the state update operation is usually performed in this function. The fn function is executed synchronously in the method body first. The return value of this function cannot be of type Future, that is, cannot be async. After the fn function is executed, the markNeedsBuild method of _Element is called.

void setState(VoidCallback fn) {
    ...
    final dynamic result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() callback argument returned a Future.'),
          ErrorDescription(
            'The setState() method on $this was called with a closure or method that '
            'returned a Future. Maybe it is marked as "async".'
          ),
          ErrorHint(
            'Instead of performing asynchronous work inside a call to setState(), first '
            'execute the work (without updating the widget state), and then synchronously '
           'update the state inside a call to setState().'),]); }return true; } ()); _element.markNeedsBuild(); }Copy the code

The Element corresponding to the StatefulWidget is a StatefulElement, and the constructor in the StatefulElement creates State through the createState of the StatefulWidget and sets the Element itself to Stat The _element property of e. State is also stored in Element’s _state property.

  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    ...
    _state._element = this; . _state._widget = widget;assert(_state._debugLifecycleState == _StateLifecycle.created);
  }
Copy the code

Element markNeedsBuild

void markNeedsBuild() {
    ...
    if(! _active)return; .if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }
Copy the code

The markNeedsBuild method calls owner’s scheduleBuildFor method, marks the element as dirty and adds it to a global list of elements that need to be updated. Owner is the BuildOwner object.

BuildOwner scheduleBuildFor

void scheduleBuildFor(Element element) {
    ...
    if (element._inDirtyList) {
      ...
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if(! _scheduledFlushDirtyElements && onBuildScheduled ! =null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element); 
    element._inDirtyList = true; . }Copy the code

This method performs several tasks

  1. judgeelementWhether you have joined the_dirtyElementsList. If it is already in the list, it is returned directly without performing the following operations.
  2. judge_scheduledFlushDirtyElementsWhether it isfalse, this variable indicates whether the rebuild process is currently underway_dirtyElementsElement in. If no rebuilding is underway, andonBuildScheduledIf the callback is not empty, it is calledonBuildScheduledFunction.
  3. Add element to_dirtyElements, and tag Element’s_inDirtyListfortrueIs added to the dirty elements list.

BuildOwner is created in the initInstances method of the WdigetBinding. And set the onBuildScheduled callback to the _handleBuildScheduled method of WidgetsBinding. So scheduleBuildFor calls WidgetsBinding’s _handleBuildScheduled method.

WdigetBinding _handleBuildScheduled

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this; .// Initialization of [_buildOwner] has to be done after
    // [super.initInstances] is called, as it requires [ServicesBinding] to
    // properly setup the [defaultBinaryMessenger] instance.
    _buildOwner = BuildOwner();
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }
Copy the code
void _handleBuildScheduled() {
    // If we're in the process of building dirty elements, then changes
    // should not trigger a new frame.. ensureVisualUpdate(); }Copy the code

Call ensureVisualUpdate in _handleBuildScheduled. Note that ensureVisualUpdate is not a method in a WidgetsBinding, but a method in a SchedulerBinding. Both WidgetsBinding and SchedulerBinding are mixins that are integrated into the WidgetsFlutterBinding class and initialized when the application starts executing the runApp function. In DART, a class introduces multiple mixins at the same time, with the rightmost having a higher priority, based on the order of with. Mixins have a linearization process. If the right-hand mixin overrides a method and calls super.overrideMethod() in the overridden method, the corresponding method of the left-hand mixin is called.

‘Mixins in Dart are implemented by creating a new class that layers the implementation of Mixins on top of a superclass to create a new class that is not “in the superclass” but “on top” of the superclass, so there is no ambiguity about how to solve the lookup problem. — Lasse R. H. Nielsen on StackOverflow.’

class WidgetsFlutterBinding extends BindingBase with GestureBinding.ServicesBinding.SchedulerBinding.PaintingBinding.SemanticsBinding.RendererBinding.WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    returnWidgetsBinding.instance; }}Copy the code

SchedulerBinding scheduleFrame

void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return; }}Copy the code

The ensureVisualUpdate method determines the current refresh status through the SchedulerPhase enumeration class. MidFrameMicrotasks – – persistentCallbacks -> postFrameCallbacks -> Idle You know that the actual refresh process is done at the persistentCallbacks state. So, if the last refresh is complete (postFrameCallbacks or idle state), the scheduleFrame request is called to refresh again.

void scheduleFrame() {
    if(_hasScheduledFrame || ! framesEnabled)return; . ensureFrameCallbacksRegistered();window.scheduleFrame();
    _hasScheduledFrame = true;
  }
Copy the code

WidgetBinding scheduleFrame will first call ensureFrameCallbacksRegistered method to ensure the window back DiaoHan tens of being registered. Then call the window scheduleFrame method.

void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ?? = _handleBeginFrame;window.onDrawFrame ?? = _handleDrawFrame; }Copy the code
/// Requests that, at the next appropriate opportunity, the [onBeginFrame]
/// and [onDrawFrame] callbacks be invoked.
void scheduleFrame() native 'Window_scheduleFrame';
Copy the code

Window scheduleFrame method is a native method. It can be seen from the above comment that the onBeginFrame callback and onDrawFrame callback are called after calling this method. This has gone through two callback ensureFrameCallbacksRegistered set to WidgetBinding _handleBeginFrame and _handleDrawFrame method. Let’s focus on the _handleDrawFrame method.

void _handleDrawFrame() {
    if (_ignoreNextEngineDrawFrame) {
      _ignoreNextEngineDrawFrame = false;
      return;
    }
    handleDrawFrame();
  }
Copy the code
/// Called by the engine to produce a new frame.
///
/// This method is called immediately after [handleBeginFrame]. It calls all
/// the callbacks registered by [addPersistentFrameCallback], which typically
/// drive the rendering pipeline, and then calls the callbacks registered by
/// [addPostFrameCallback].
void handleDrawFrame() {
    ...
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally{ _schedulerPhase = SchedulerPhase.idle; . _currentFrameTimeStamp =null; }}Copy the code

The handleDrawFrame method is called by the engine to create a new frame. The method flow is also fairly clear. The callback in _persistentCallbacks is first iterated, The callback can be WidgetsBinding. Instance. AddPersistentFrameCallback (fn) registered; Then, make a copy of the _postFrameCallbacks file and clear the list of _postFrameCallbacks. The _postFrameCallbacks file will hold the redrawn callback function and execute it only once. Can pass WidgetsBinding. Instance. AddPostFrameCallback (fn) add a callback. After executing _persistentCallbacks and _postFrameCallbacks, the state is set to SchedulerPhase.idle to indicate that it has been flushed.

Through annotation can know is driven by addPersistentFrameCallback rendering. A search shows that the persistentFrameCallback callback is registered in the initInstances method of the RendererBinding.

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances(); . initRenderView(); _handleSemanticsEnabledChanged();assert(renderView ! =null); addPersistentFrameCallback(_handlePersistentFrameCallback); initMouseTracker(); }}Copy the code

Directly in the _handlePersistentFrameCallback callback function invoked the drawFrame method.

void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _mouseTracker.schedulePostFrameCheck();
  }
Copy the code
@protected
void drawFrame() {
    assert(renderView ! =null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
        renderView.compositeFrame(); // this sends the bits to the GPU
        pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
        _firstFrameSent = true; }}Copy the code

Note that WidgetsBinding also implements drawFrame, and the WidgetsBinding is at the far right when mixin to the WidgetsFlutterBinding class, so its method has the highest priority. _handlePersistentFrameCallback invoked in drawFrame method, would call the drawFrame WidgetsBinding method first.

WidgetsBinding drawFrame

@override
  void drawFrame() {
    ...  
    try {
      if(renderViewElement ! =null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    ...
  }
Copy the code

In the WidgetsBinding drawFrame method, buildOwner’s buildScope method is called first and then super.drawFrame() is called, The drawFrame method of RendererBinding can be called with super.drawframe (). Look at buildOwner’s buildScope method first.

BuildOwner buildScope

void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return; .try {
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        ...
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            // It is possible for previously dirty but inactive widgets to move right in the list.
            // We therefore have to move the index left in the list to account for this.
            // We don't know how many could have moved. However, we do know that the only possible
            // change to the list is that nodes that were previously to the left of the index have
            // now moved to be to the right of the right-most cleaned node, and we do know that
            // all the clean nodes were to the left of the index. So we move the index left
            // until just after the right-most clean node.
            index -= 1; }}}... }finally {
      for (final Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null; . }}Copy the code

The core logic of buildScope is to first sort _dirtyElements by depth, then iterate through the _dirtyElements list and call the rebuild method on the elements. The rebuild method is defined in the Element class.

voidrebuild() { ... performRebuild(); . }Copy the code
@protected
  void performRebuild();
Copy the code

PerformRebuild is an abstract method in the Element class that each subclass implements. StateElement’s parent class is ComponentElement, so look at the performRebuild method for ComponentElement

@override
  void performRebuild() {
    ...
    Widget built;
    try{.. built = build(); . }catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this)); },),); }finally{... }try {
      _child = updateChild(_child, built, slot);
      assert(_child ! =null);
    } catch (e, stack) {
      ...
      _child = updateChild(null, built, slot); }... }Copy the code

In this method, the Widget is created by calling the Build method directly, and if the build method raises an exception, an ErrorWidget is created, which is the red warning screen you often see. After the build method is called, updateChild(_Child, built, slot) is called to update the child Widget. StatelessElement and StatefulElement override the build methods to call the Widget and State build methods, respectively.

///StatelessElement
@override
Widget build() => widget.build(this);
Copy the code
@override
Widget build() => _state.build(this);
Copy the code

The drawFrame method of the WidgetsBinding is called by super.drawframe () into the RendererBinding drawFrame method.

@protected
  void drawFrame() {
    assert(renderView ! =null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true; }}Copy the code

The RendererBinding drawFrame method updates the UI using the pipelineOwner object to rearrange layout and paint.

conclusion

The StatefulWidget adds its corresponding StatefulElement to BuildOwner’s dirtyElements via the setState method and triggers a refresh. After receiving the refresh callback, iterate over the elements in dirtyElements and perform the rebuild operation to update the display status.