setChildren method
Adds children to a node.
The children are added but not visible until the parent is expanded. If the parent already has children, the old children and their descendants are purged from all data structures first.
Implementation
void setChildren(TKey parentKey, List<TreeNode<TKey, TData>> children) {
assert(_hasKey(parentKey), 'Parent node $parentKey not found');
assert(
!_isPendingDeletion(parentKey),
'Cannot setChildren on $parentKey while it is animating out '
'(pending deletion). The parent will be purged when its exit animation '
'completes, leaving the new children orphaned.',
);
final seen = <TKey>{};
for (final child in children) {
if (!seen.add(child.key)) {
throw ArgumentError(
"Duplicate key ${child.key} in setChildren($parentKey)",
);
}
if (child.key == parentKey) {
throw ArgumentError(
"setChildren($parentKey): child key ${child.key} equals parentKey "
"(a node cannot be its own child)",
);
}
// Reject keys that already exist under a different parent — silently
// overwriting _childListOf(child.key) = [] below would orphan the existing
// subtree and leave a stale reference in the old parent's child list.
// Accept when the key is already a child of this same parent (no-op
// reparent — handled by the purge-old-children step).
if (_hasKey(child.key) && _parentKeyOfKey(child.key) != parentKey) {
throw ArgumentError(
"setChildren($parentKey): key ${child.key} already exists under "
"parent ${_parentKeyOfKey(child.key)}. Use moveNode() or remove() first.",
);
}
}
// Purge old children and their descendants before overwriting.
final oldChildren = _childListOf(parentKey);
if (oldChildren != null && oldChildren.isNotEmpty) {
final allOldKeys = <TKey>[];
for (final oldChildKey in oldChildren) {
allOldKeys.add(oldChildKey);
_getDescendantsInto(oldChildKey, allOldKeys);
}
// Check visibility and contiguity BEFORE purging (purge clears the index)
int minIdx = _order.length;
int maxIdx = -1;
int visibleCount = 0;
for (final key in allOldKeys) {
final idx = _order.indexOf(key);
if (idx != VisibleOrderBuffer.kNotVisible) {
visibleCount++;
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
}
// Decrement the parent's visible-subtree-size cache by the
// count of visible old descendants BEFORE _purgeNodeData
// releases their nids. Mirrors the fix in _removeNodesImmediate
// and _finalizeAnimation: the deferred order-buffer compaction
// below cannot fire useful visibility-loss callbacks once the
// released nids' parent chains are cleared.
if (visibleCount > 0) {
final parentNid = _nids[parentKey];
if (parentNid != null) {
_bumpVisibleSubtreeSizeFromSelf(parentNid, -visibleCount);
}
}
final oldKeySet = allOldKeys.toSet();
for (final key in allOldKeys) {
_purgeNodeData(key);
}
if (visibleCount > 0) {
_runWithSubtreeSizeUpdatesSuppressed(() {
if (maxIdx - minIdx + 1 == visibleCount) {
// Contiguous removal
_order.removeRange(minIdx, maxIdx + 1);
_updateIndicesAfterRemove(minIdx);
} else {
// Non-contiguous removal
_order.removeWhereKeyIn(oldKeySet);
_rebuildVisibleIndex();
}
});
_structureGeneration++;
}
}
final parentDepth = _depthOfKey(parentKey);
final childIds = <TKey>[];
final sorted = comparator != null
? (List.of(children)..sort(comparator))
: children;
for (final child in sorted) {
_adoptKey(child.key);
_store.setData(child.key, child);
_setParentKey(child.key, parentKey);
_setChildList(child.key, []);
_setDepthKey(child.key, parentDepth + 1);
_setExpandedKey(child.key, false);
childIds.add(child.key);
}
_setChildList(parentKey, childIds);
// If parent is expanded and visible, insert new children into the
// visible order so they render immediately.
if (_isExpandedKey(parentKey) && childIds.isNotEmpty) {
final parentIdx = _order.indexOf(parentKey);
if (parentIdx != VisibleOrderBuffer.kNotVisible) {
final insertIdx = parentIdx + 1;
_order.insertAllKeys(insertIdx, childIds);
_updateIndicesFrom(insertIdx);
_structureGeneration++;
}
}
// Bulk child replacement: old children (and their subtrees) were purged,
// new children registered. Any retained row under [parentKey] may have
// its builder output differ — fall back to a full refresh.
_notifyStructural();
}