updateChild method Null safety
Update the given child with the given new configuration.
This method is the core of the widgets system. It is called each time we are to add, update, or remove a child based on an updated configuration.
The newSlot
argument specifies the new value for this element's slot.
If the child
is null, and the newWidget
is not null, then we have a new
child for which we need to create an Element, configured with newWidget
.
If the newWidget
is null, and the child
is not null, then we need to
remove it because it no longer has a configuration.
If neither are null, then we need to update the child
's configuration to
be the new configuration given by newWidget
. If newWidget
can be given
to the existing child (as determined by Widget.canUpdate), then it is so
given. Otherwise, the old child needs to be disposed and a new child
created for the new configuration.
If both are null, then we don't have a child and won't have a child, so we do nothing.
The updateChild method returns the new child, if it had to create one, or the child that was passed in, if it just had to update the child, or null, if it removed the child and did not replace it.
The following table summarizes the above:
newWidget == null | newWidget != null | |
---|---|---|
child == null | Returns null. | Returns new Element. |
child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new Element. |
The newSlot
argument is used only if newWidget
is not null. If child
is null (or if the old child cannot be updated), then the newSlot
is
given to the new Element that is created for the child, via
inflateWidget. If child
is not null (and the old child can be
updated), then the newSlot
is given to updateSlotForChild to update
its slot, in case it has moved around since it was last built.
See the RenderObjectElement documentation for more information on slots.
Implementation
@protected
@pragma('vm:prefer-inline')
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {
if (child != null) {
deactivateChild(child);
}
return null;
}
final Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
// When the type of a widget is changed between Stateful and Stateless via
// hot reload, the element tree will end up in a partially invalid state.
// That is, if the widget was a StatefulWidget and is now a StatelessWidget,
// then the element tree currently contains a StatefulElement that is incorrectly
// referencing a StatelessWidget (and likewise with StatelessElement).
//
// To avoid crashing due to type errors, we need to gently guide the invalid
// element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
// returns false which prevents us from trying to update the existing element
// incorrectly.
//
// For the case where the widget becomes Stateful, we also need to avoid
// accessing `StatelessElement.widget` as the cast on the getter will
// cause a type error to be thrown. Here we avoid that by short-circuiting
// the `Widget.canUpdate` check once `hasSameSuperclass` is false.
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
// We don't insert a timeline event here, because otherwise it's
// confusing that widgets that "don't update" (because they didn't
// change) get "charged" on the timeline.
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
if (isTimelineTracked) {
Map<String, String>? debugTimelineArguments;
assert(() {
if (kDebugMode && debugEnhanceBuildTimelineArguments) {
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
}
return true;
}());
Timeline.startSync(
'${newWidget.runtimeType}',
arguments: debugTimelineArguments,
);
}
child.update(newWidget);
if (isTimelineTracked) {
Timeline.finishSync();
}
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
// The [debugProfileBuildsEnabled] code for this branch is inside
// [inflateWidget], since some [Element]s call [inflateWidget] directly
// instead of going through [updateChild].
newChild = inflateWidget(newWidget, newSlot);
}
} else {
// The [debugProfileBuildsEnabled] code for this branch is inside
// [inflateWidget], since some [Element]s call [inflateWidget] directly
// instead of going through [updateChild].
newChild = inflateWidget(newWidget, newSlot);
}
assert(() {
if (child != null) {
_debugRemoveGlobalKeyReservation(child);
}
final Key? key = newWidget.key;
if (key is GlobalKey) {
assert(owner != null);
owner!._debugReserveGlobalKeyFor(this, newChild, key);
}
return true;
}());
return newChild;
}