track method

List<TrackedCollectionItem> track(
  1. List values, {
  2. required bool same(
    1. dynamic a,
    2. dynamic b
    ),
})

Implementation

List<TrackedCollectionItem> track(
  List<dynamic> values, {
  required bool Function(dynamic a, dynamic b) same,
}) {
  Set<int> used = <int>{};
  List<TrackedCollectionItem?> next = List<TrackedCollectionItem?>.filled(
    values.length,
    null,
    growable: false,
  );
  List<int> unmatched = <int>[];

  // First pass: keep ids for unchanged values, preferring the nearest
  // previous index to reduce churn with duplicates.
  for (int newIndex = 0; newIndex < values.length; newIndex++) {
    dynamic value = values[newIndex];
    int matched = -1;
    int bestDistance = 1 << 30;
    for (int i = 0; i < _last.length; i++) {
      if (used.contains(i)) {
        continue;
      }

      if (!same(_last[i].value, value)) {
        continue;
      }

      int distance = (i - newIndex).abs();
      if (distance < bestDistance) {
        bestDistance = distance;
        matched = i;
        if (distance == 0) {
          break;
        }
      }
    }

    if (matched >= 0) {
      used.add(matched);
      next[newIndex] = TrackedCollectionItem(
        id: _last[matched].id,
        value: value,
      );
    } else {
      unmatched.add(newIndex);
    }
  }

  // Second pass: if a value changed in place (like typing in a text field),
  // retain the id at that same index so focus/state are preserved.
  for (int newIndex in unmatched) {
    if (newIndex < _last.length && !used.contains(newIndex)) {
      used.add(newIndex);
      next[newIndex] = TrackedCollectionItem(
        id: _last[newIndex].id,
        value: values[newIndex],
      );
    }
  }

  // Final pass: assign new ids for true inserts.
  for (int i = 0; i < next.length; i++) {
    next[i] ??= TrackedCollectionItem(id: _nextId++, value: values[i]);
  }

  List<TrackedCollectionItem> finalized = next.cast<TrackedCollectionItem>();
  _last = finalized;
  return finalized;
}