mutateListItem<Id> method
Mutates a cached list with server response updating the item.
Similar to mutateList, but send returns the server-confirmed item.
After successful sync, the item in the list is updated with the server
response. This is useful when the server assigns IDs or modifies the item.
Parameters:
key: The cache key for the list.operation: The list operation to apply. For operations that add items (append, prepend, insert), the item must have anidSelectormatch.send: An async function that persists the change and returns the server-confirmed item. The item fromoperationis passed as argument.idSelector: A function that extracts a unique identifier from an item. Used to find and update the optimistic item with the server response.invalidates: Optional list of cache keys (supports glob patterns) to invalidate after successful sync.invalidateTags: Optional list of tags to invalidate after successful sync.
Example:
// Server assigns an ID to the new event
await cache.mutateListItem(
key: 'events',
operation: ListOperation.append(Event(id: 'temp', title: 'New Event')),
send: (event) async {
final response = await api.createEvent(event);
return Event.fromJson(response);
},
idSelector: (e) => e.id,
);
For UpdateWhereOperation, the send function receives the first
matching updated item. The server response updates all items that match
the idSelector.
For RemoveWhereOperation, send is called with a dummy value and
its return is ignored (since items are being removed).
Throws CacheMissException if no cached value exists for key.
Throws StateError if send is called but no item is available
from the operation.
Implementation
Future<void> mutateListItem<Id>({
required String key,
required ListOperation<T> operation,
required Future<T> Function(T item) send,
required Id Function(T item) idSelector,
List<String>? invalidates,
List<String>? invalidateTags,
}) async {
await mutate(
key: key,
mutation: Mutation<List<T>>(
apply: operation.apply,
send: (list) async {
// Handle RemoveWhereOperation specially - nothing to update
if (operation is RemoveWhereOperation<T>) {
// For remove, we don't have an item to send, but we still call
// send with a placeholder. The caller should handle this.
// Actually, for remove operations, we use mutateList instead.
return list;
}
// Get the item from the operation
final operationItem = operation.item;
if (operationItem == null) {
// UpdateWhereOperation: find the first matching item in the list
if (operation is UpdateWhereOperation<T>) {
// Find first item that was updated (exists in new list)
for (final item in list) {
// We can't know which items were updated without the original
// list, so we'll send the first item that matches the predicate
// applied to the current list
final serverItem = await send(item);
final serverId = idSelector(serverItem);
// Update all items with matching ID
return list.map((i) {
if (idSelector(i) == serverId) {
return serverItem;
}
return i;
}).toList();
}
return list;
}
throw StateError('No item available from operation');
}
// Send the item and get server response
final serverItem = await send(operationItem);
final optimisticId = idSelector(operationItem);
final serverId = idSelector(serverItem);
// Update the item in the list with server response
return list.map((item) {
if (idSelector(item) == optimisticId ||
idSelector(item) == serverId) {
return serverItem;
}
return item;
}).toList();
},
),
invalidates: invalidates,
invalidateTags: invalidateTags,
);
}