updateChildren method

void updateChildren(
  1. List<Widget> newWidgets
)

Reconcile old children with new widgets using Flutter's algorithm.

Matching strategy:

  • Walk from the top matching by Widget.canUpdate (runtimeType + key).
  • Walk from the bottom matching the same way.
  • For the middle section, build a keyed map from old children that have explicit keys. Unkeyed old children in the middle are unmounted.
  • Walk the middle of the new list, looking up matches by key.
  • Unmount any remaining unmatched keyed old children.

Implementation

void updateChildren(List<Widget> newWidgets) {
  if (_children.isEmpty && newWidgets.isEmpty) return;

  var structureChanged = false;

  int newChildrenTop = 0;
  int oldChildrenTop = 0;
  int newChildrenBottom = newWidgets.length - 1;
  int oldChildrenBottom = _children.length - 1;

  final newChildren = List<Element?>.filled(newWidgets.length, null);

  // Scan from the top: match while canUpdate holds.
  while (oldChildrenTop <= oldChildrenBottom &&
      newChildrenTop <= newChildrenBottom) {
    final oldChild = _children[oldChildrenTop];
    final newWidget = newWidgets[newChildrenTop];
    if (!Widget.canUpdate(oldChild.widget, newWidget)) break;
    oldChild.update(newWidget);
    newChildren[newChildrenTop] = oldChild;
    newChildrenTop++;
    oldChildrenTop++;
  }

  // Scan from the bottom: match while canUpdate holds.
  while (oldChildrenTop <= oldChildrenBottom &&
      newChildrenTop <= newChildrenBottom) {
    final oldChild = _children[oldChildrenBottom];
    final newWidget = newWidgets[newChildrenBottom];
    if (!Widget.canUpdate(oldChild.widget, newWidget)) break;
    // Don't update yet — just narrow the window. We update bottom matches
    // after the middle is resolved so indices are stable.
    oldChildrenBottom--;
    newChildrenBottom--;
  }

  // Build a keyed map of the remaining old children in the middle.
  final haveOldChildren = oldChildrenTop <= oldChildrenBottom;
  Map<Key, Element>? oldKeyedChildren;
  if (haveOldChildren) {
    oldKeyedChildren = <Key, Element>{};
    while (oldChildrenTop <= oldChildrenBottom) {
      final oldChild = _children[oldChildrenTop];
      if (oldChild.widget.key != null) {
        oldKeyedChildren[oldChild.widget.key!] = oldChild;
      } else {
        // Unkeyed old children in the middle cannot be matched — unmount.
        oldChild.unmount();
        structureChanged = true;
      }
      oldChildrenTop++;
    }
  }

  // Walk the middle of the new list, matching by key.
  while (newChildrenTop <= newChildrenBottom) {
    Element? oldChild;
    final newWidget = newWidgets[newChildrenTop];
    if (newWidget.key != null && oldKeyedChildren != null) {
      oldChild = oldKeyedChildren[newWidget.key!];
      if (oldChild != null) {
        if (Widget.canUpdate(oldChild.widget, newWidget)) {
          oldKeyedChildren.remove(newWidget.key!);
        } else {
          oldChild = null;
        }
      }
    }

    if (oldChild != null) {
      oldChild.update(newWidget);
      newChildren[newChildrenTop] = oldChild;
    } else {
      final element = createElement(newWidget);
      element._attachOwner(_owner);
      element.mount(this);
      newChildren[newChildrenTop] = element;
      structureChanged = true;
    }
    newChildrenTop++;
  }

  // Now update the bottom matches (we deferred these earlier).
  assert(
    newWidgets.length - newChildrenTop == _children.length - oldChildrenTop,
  );
  newChildrenBottom = newWidgets.length - 1;
  oldChildrenBottom = _children.length - 1;
  while (newChildrenTop <= newChildrenBottom) {
    final oldChild = _children[oldChildrenTop];
    final newWidget = newWidgets[newChildrenTop];
    assert(Widget.canUpdate(oldChild.widget, newWidget));
    oldChild.update(newWidget);
    newChildren[newChildrenTop] = oldChild;
    newChildrenTop++;
    oldChildrenTop++;
  }

  // Unmount leftover keyed old children that were not reused.
  if (oldKeyedChildren != null && oldKeyedChildren.isNotEmpty) {
    for (final oldChild in oldKeyedChildren.values) {
      oldChild.unmount();
      structureChanged = true;
    }
  }

  _children
    ..clear()
    ..addAll(newChildren.cast<Element>());

  if (structureChanged) {
    final host = _renderObjectHost();
    if (host != null && !host._isRebuilding) {
      host._markDirty();
    }
  }
}