rebuild method

void rebuild({
  1. bool force = false,
})

Cause the widget to update itself. In debug builds, also verify various invariants.

Called by the BuildOwner when BuildOwner.scheduleBuildFor has been called to mark this element dirty, by mount when the element is first built, and by update when the widget has changed.

The method will only rebuild if dirty is true. To rebuild regardless of the dirty flag, set force to true. Forcing a rebuild is convenient from update, during which dirty is false.

When rebuilds happen

Terminology

Widgets represent the configuration of Elements. Each Element has a widget, specified in Element.widget. The term "widget" is often used when strictly speaking "element" would be more correct.

While an Element has a current Widget, over time, that widget may be replaced by others. For example, the element backing a ColoredBox may first have as its widget a ColoredBox whose ColoredBox.color is blue, then later be given a new ColoredBox whose color is green.

At any particular time, multiple Elements in the same tree may have the same Widget. For example, the same ColoredBox with the green color may be used in multiple places in the widget tree at the same time, each being backed by a different Element.

Marking an element dirty

An Element can be marked dirty between frames. This can happen for various reasons, including the following:

Rebuilding

Dirty elements are rebuilt during the next frame. Precisely how this is done depends on the kind of element. A StatelessElement rebuilds by using its widget's StatelessWidget.build method. A StatefulElement rebuilds by using its widget's state's State.build method. A RenderObjectElement rebuilds by updating its RenderObject.

In many cases, the end result of rebuilding is a single child widget or (for MultiChildRenderObjectElements) a list of children widgets.

These child widgets are used to update the widget property of the element's child (or children) elements. The new Widget is considered to correspond to an existing Element if it has the same Type and Key. (In the case of MultiChildRenderObjectElements, some effort is put into tracking widgets even when they change order; see RenderObjectElement.updateChildren.)

If there was no corresponding previous child, this results in a new Element being created (using Widget.createElement); that element is then itself built, recursively.

If there was a child previously but the build did not provide a corresponding child to update it, then the old child is discarded (or, in cases involving GlobalKey reparenting, reused elsewhere in the element tree).

The most common case, however, is that there was a corresponding previous child. This is handled by asking the child Element to update itself using the new child Widget. In the case of StatefulElements, this is what triggers State.didUpdateWidget.

Not rebuilding

Before an Element is told to update itself with a new Widget, the old and new objects are compared using operator ==.

In general, this is equivalent to doing a comparison using identical to see if the two objects are in fact the exact same instance. If they are, and if the element is not already marked dirty for other reasons, then the element skips updating itself as it can determine with certainty that there would be no value in updating itself or its descendants.

It is strongly advised to avoid overriding operator == on Widget objects. While doing so seems like it could improve performance, in practice, for non-leaf widgets, it results in O(N²) behavior. This is because by necessity the comparison would have to include comparing child widgets, and if those child widgets also implement operator ==, it ultimately results in a complete walk of the widget tree... which is then repeated at each level of the tree. In practice, just rebuilding is cheaper. (Additionally, if any subclass of Widget used in an application implements operator ==, then the compiler cannot inline the comparison anywhere, because it has to treat the call as virtual just in case the instance happens to be one that has an overridden operator.)

Instead, the best way to avoid unnecessary rebuilds is to cache the widgets that are returned from State.build, so that each frame the same widgets are used until such time as they change. Several mechanisms exist to encourage this: const widgets, for example, are a form of automatic caching (if a widget is constructed using the const keyword, the same instance is returned each time it is constructed with the same arguments).

Another example is the AnimatedBuilder.child property, which allows the non-animating parts of a subtree to remain static even as the AnimatedBuilder.builder callback recreates the other components.

Implementation

@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('wasm:prefer-inline')
void rebuild({bool force = false}) {
  assert(_lifecycleState != _ElementLifecycle.initial);
  if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {
    return;
  }
  assert(() {
    debugOnRebuildDirtyWidget?.call(this, _debugBuiltOnce);
    if (debugPrintRebuildDirtyWidgets) {
      if (!_debugBuiltOnce) {
        debugPrint('Building $this');
        _debugBuiltOnce = true;
      } else {
        debugPrint('Rebuilding $this');
      }
    }
    return true;
  }());
  assert(_lifecycleState == _ElementLifecycle.active);
  assert(owner!._debugStateLocked);
  Element? debugPreviousBuildTarget;
  assert(() {
    debugPreviousBuildTarget = owner!._debugCurrentBuildTarget;
    owner!._debugCurrentBuildTarget = this;
    return true;
  }());
  try {
    performRebuild();
  } finally {
    assert(() {
      owner!._debugElementWasRebuilt(this);
      assert(owner!._debugCurrentBuildTarget == this);
      owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
      return true;
    }());
  }
  assert(!_dirty);
}