Widgets
What are the widget building blocks?
The main entry points are
RenderObjectWidget,StatefulWidget, andStatelessWidget. Widgets that export data to one or more descendant widgets (via notifications or another mechanism) utilizeProxyWidgetor its subclasses,InheritedWidgetandParentDataWidget.In general, widgets either directly or indirectly configure render objects; these indirect “component” widgets (e.g., stateful and stateless widgets) participate in a building process, whereas render object widgets manage an associated render object directly (creating it and updating it via
RenderObjectWidget.createRenderObjectandRenderObjectWidget.updateRenderObject, respectively).Certain widgets wrap (i.e., reproject) a child widget (
ProxyWidget), introducing heritable state (InheritedWidget,InheritedModel) or setting parent data used by an enclosing widget (ParentDataWidget).ProxyWidgetnotifies clients (viaProxyElement.notifyClients) in response to widget changes (viaProxyElement.updated, called byProxyElement.update).ParentDataWidgetutilizes this flow to update the first descendant render object’s parent data (viaParentDataElement._applyParentData, which callsRenderObjectElement._updateParentData); this will be triggered any time the widget is updated.
PreferredSizeWidgetis not widely used but an interesting example of adapting widgets to a specific need (e.g., anAppBarexpressing a size preference).Numerous helper subclasses exist, most notably including
SingleChildRenderObjectWidget,MultiChildRenderObjectWidget,LeafRenderObjectWidget. These provide storage for a child / children without implementing the underlying child model.Anonymous widgets can be created using
BuilderandStatefulBuilder.
How does building work?
Every widget is associated with an element, a mutable object corresponding to the widget’s instantiation within the tree. Only those widgets associated with
ComponentElement(e.g.,StatelessWidget,StatefulWidget,ProxyWidget) participate in the build process;RenderObjectWidgetsubclasses, generally associated withRenderObjectElements, do not (these simply update their render object when building).ComponentElementinstances only have a single child, typically that returned by their widget’s build method.When the element tree is first affixed to the render tree via
RenderObjectToWidgetAdapter.attachToRenderTree, theRenderObjectToWidgetElement(itself aRootRenderObjectElement) assigns aBuildOwnerfor the element tree. TheBuildOwneris responsible for tracking dirty elements (BuildOwner.scheduleBuildFor), establishing build scopes wherein elements can be rebuilt / descendent elements can be marked dirty (BuildOwner.buildScope), and unmounting inactive elements at the end of a frame (BuildOwner.finalizeTree). It also maintains a reference to the rootFocusManagerand triggers reassembly after a hot reload.When a
ComponentElementis mounted (e.g., after being inflated), an initial build is performed immediately (viaComponentElement._firstBuild, which callsComponentElement.rebuild).Later, elements can be marked dirty using
Element.markNeedsBuild. This is invoked any time the UI might need to be updated reactively (or explicitly, in response toState.setState). This method adds the element to the dirty list and, viaBuildOwner.onBuildScheduled, schedules a frame viaSchedulerBinding.ensureVisualUpdate.Other operations trigger a rebuild directly (i.e., without marking the tree dirty first). These include
ProxyElement.update,StatelessElement.update,StatefulElement.update, andComponentElement.mount(for the initial build, only).In contrast, builds are scheduled by
State.setState,Element.reassemble,Element.didChangeDependencies,StatefulElement.activate, etc.Proxy elements use notifications to indicate when underlying data has changed. In the case of
InheritedElement, each dependant’sElement.didChangeDependenciesis invoked which, by default, marks that element as dirty.
Once per frame,
BuildOwner.buildScopewill walk the element tree in depth-first order, only considering those nodes that have been marked dirty. By locking the tree and iterating in depth first order, any nodes that become dirty while rebuilding must necessarily be lower in the tree; this is because building is a unidirectional process -- a child cannot mark its parent as being dirty. Thus, it is not possible for build cycles to be introduced and it is not possible for elements that have been marked clean to become dirty again.As the build progresses,
ComponentElement.performRebuilddelegates to theComponentElement.buildmethod to produce a new child widget for each dirty element. Next,Element.updateChildis invoked to efficiently reuse or recreate an element for the child. Crucially, if the child’s widget hasn’t changed, the build is immediately cut off. Note that if the child widget did change andElement.updateis needed, that child will itself be marked dirty, and the build will continue down the tree.Each
Elementmaintains a map of allInheritedElementancestors at its location. Thus, accessing dependencies from the build process is a constant time operation.If
Element.updateChildinvokesElement.deactivateChildbecause a child is removed or moved to another part of the tree,BuildOwner.finalizeTreewill unmount the element if it isn’t reintegrated by the end of the frame.
How do stateful widgets work?
StatefulWidgetis associated withStatefulElement, aComponentElementthat is almost identical toStatelessElement. The key difference is that theStatefulElementreferences theStateof the correspondingStatefulWidget, and invokes lifecycle methods on that instance at key moments. For instance, whenStatefulElement.updateis invoked, theStateinstance is notified viaState.didUpdateWidget.StatefulElementcreates the associatedStateinstance when it is constructed (i.e., inStatefulWidget.createElement). Then, when theStatefulElementis built for the first time (viaStatefulElement._firstBuild, called byStatefulElement.mount),State.initStateis invoked. Crucially,Stateinstance and theStatefulWidgetshare the same element.Since
Stateis associated with the underlyingStatefulElement, if the widget changes, provided thatStatefulElement.updateChildis able to reuse the same element (because the widget’s runtime type and key both match),Statewill be preserved. Otherwise, theStatewill be recreated.
Why is changing tree depth expensive?
Changing the depth necessarily causes a rebuild of the subtree, since there will never be an element match for the new child (since it wasn’t a child previously).
This requires
Element.inflateWidgetto be invoked, which causes a fresh build, which causes layout, paint, etc.Adding a
GlobalKeyto the previous child can mitigate this issue sinceElement.updateChildis able to reuse elements that are stored in theGlobalKeyregistry (allowing that subtree to simply be reinserted instead of rebuilt).
Last updated
Was this helpful?