updateChildren method
Updates the children of this element to use new components.
Attempts to update the given old children list using the given new components, removing obsolete elements and introducing new ones as necessary, and then returns the new child list.
During this function the oldChildren
list must not be modified. If the
caller wishes to remove elements from oldChildren
re-entrantly while
this function is on the stack, the caller can supply a forgottenChildren
argument, which can be modified while this function is on the stack.
Whenever this function reads from oldChildren
, this function first
checks whether the child is in forgottenChildren
. If it is, the function
acts as if the child was not in oldChildren
.
This function is a convenience wrapper around updateChild, which updates each individual child.
Implementation
@protected
List<Element> updateChildren(List<Element> oldChildren, List<Component> newComponents,
{Set<Element>? forgottenChildren}) {
Element? replaceWithNullIfForgotten(Element? child) {
return child != null && forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
}
// This attempts to diff the new child list (newComponents) with
// the old child list (oldChildren), and produce a new list of elements to
// be the new list of child elements of this element. The called of this
// method is expected to update this render object accordingly.
// The cases it tries to optimize for are:
// - the old list is empty
// - the lists are identical
// - there is an insertion or removal of one or more components in
// only one place in the list
// If a component with a key is in both lists, it will be synced.
// Components without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the top, syncing nodes, until you no longer have
// matching nodes.
// 2. Walk the lists from the bottom, without syncing nodes, until you no
// longer have matching nodes. We'll sync these nodes at the end. We
// don't sync them now because we want to sync all the nodes in order
// from beginning to end.
// At this point we narrowed the old and new lists to the point
// where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list forwards:
// * Sync non-keyed items with null
// * Sync keyed items with the source if it exists, else with null.
// 5. Walk the bottom of the list again, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still
// mounted.
if (oldChildren.length <= 1 && newComponents.length <= 1) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren.firstOrNull);
var newChild = updateChild(oldChild, newComponents.firstOrNull, null);
return [if (newChild != null) newChild];
}
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newComponents.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
final List<Element?> newChildren = oldChildren.length == newComponents.length
? oldChildren
: List<Element?>.filled(newComponents.length, null, growable: true);
Element? prevChild;
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
final Component newComponent = newComponents[newChildrenTop];
if (oldChild == null || !Component.canUpdate(oldChild.component, newComponent)) break;
final Element newChild = updateChild(oldChild, newComponent, prevChild)!;
newChildren[newChildrenTop] = newChild;
prevChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
final Component newComponent = newComponents[newChildrenBottom];
if (oldChild == null || !Component.canUpdate(oldChild.component, newComponent)) break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
Map<Key, Element>? retakeOldKeyedChildren;
if (newChildrenTop <= newChildrenBottom && oldChildrenTop <= oldChildrenBottom) {
final Map<Key, Component> newKeyedChildren = {};
var newChildrenTopPeek = newChildrenTop;
while (newChildrenTopPeek <= newChildrenBottom) {
final Component newComponent = newComponents[newChildrenTopPeek];
final Key? key = newComponent.key;
if (key != null) {
newKeyedChildren[key] = newComponent;
}
newChildrenTopPeek += 1;
}
if (newKeyedChildren.isNotEmpty) {
retakeOldKeyedChildren = {};
var oldChildrenTopPeek = oldChildrenTop;
while (oldChildrenTopPeek <= oldChildrenBottom) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTopPeek]);
if (oldChild != null) {
final Key? key = oldChild.component.key;
if (key != null) {
final Component? newComponent = newKeyedChildren[key];
if (newComponent != null && Component.canUpdate(oldChild.component, newComponent)) {
retakeOldKeyedChildren[key] = oldChild;
}
}
}
oldChildrenTopPeek += 1;
}
}
}
while (newChildrenTop <= newChildrenBottom) {
if (oldChildrenTop <= oldChildrenBottom) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
final Key? key = oldChild.component.key;
if (key == null || retakeOldKeyedChildren == null || !retakeOldKeyedChildren.containsKey(key)) {
deactivateChild(oldChild);
}
}
oldChildrenTop += 1;
}
Element? oldChild;
final Component newComponent = newComponents[newChildrenTop];
final Key? key = newComponent.key;
if (key != null) {
oldChild = retakeOldKeyedChildren?[key];
}
final Element newChild = updateChild(oldChild, newComponent, prevChild)!;
newChildren[newChildrenTop] = newChild;
prevChild = newChild;
newChildrenTop += 1;
}
while (oldChildrenTop <= oldChildrenBottom) {
final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
if (oldChild != null) {
final Key? key = oldChild.component.key;
if (key == null || retakeOldKeyedChildren == null || !retakeOldKeyedChildren.containsKey(key)) {
deactivateChild(oldChild);
}
}
oldChildrenTop += 1;
}
// We've scanned the whole list.
newChildrenBottom = newComponents.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
final Element oldChild = oldChildren[oldChildrenTop];
final Component newComponent = newComponents[newChildrenTop];
final Element newChild = updateChild(oldChild, newComponent, prevChild)!;
newChildren[newChildrenTop] = newChild;
prevChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
assert(newChildren.every((element) => element != null));
return newChildren.cast<Element>();
}