rebuild method
- 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:
-
The State of a StatefulWidget can cause its Element to be marked dirty by calling the State.setState method.
-
When an InheritedWidget changes, descendants that have previously subscribed to it will be marked dirty.
-
During a hot reload, every element is marked dirty (using Element.reassemble).
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);
}