一键导入
add-optimistic-command
Add optimisticCommand for blocking one-time actions with immediate UI updates and automatic rollback on failure
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add optimisticCommand for blocking one-time actions with immediate UI updates and automatic rollback on failure
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Add custom error handling with catchError to a mix() call for logging, error conversion, or suppression
Add internet connectivity checking before executing a Cubit method with optional retry
Add debounce to delay method execution until after a period of inactivity for search or validation
Add EffectQueue to state for triggering multiple sequential one-time UI effects
Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing
Add error state handling to widgets using context.isFailed() and context.getException()
| name | add-optimistic-command |
| description | Add optimisticCommand for blocking one-time actions with immediate UI updates and automatic rollback on failure |
This skill adds optimisticCommand for one-time actions with immediate UI updates and automatic rollback on failure.
Implements optimistic updates for blocking, one-time operations like:
The UI updates immediately, the server operation runs, and on failure the UI rolls back.
Use optimisticCommand when:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class TodoCubit extends Cubit<TodoState> {
TodoCubit() : super(const TodoState());
void addTodo(Todo newTodo) => optimisticCommand(
key: (AddTodo, newTodo.id),
// 1. Return the optimistic value (new list with item added)
optimisticValue: () => state.todoList.add(newTodo),
// 2. Extract the value from current state
getValueFromState: (state) => state.todoList,
// 3. Apply a value back to state
applyValueToState: (state, value) =>
state.copyWith(todoList: value as List<Todo>),
// 4. Send the command to the server
sendCommandToServer: (optimisticValue) async {
await api.saveTodo(newTodo);
return null; // Return server response if needed
},
);
}
Rollback Safety: Rollback only occurs if the current state value still matches the optimistic value. This prevents overwriting concurrent changes made by other operations. If another update changed the value while the command was running, rollback is skipped to preserve that newer change.
| Parameter | Purpose |
|---|---|
key | Identifies the operation for state tracking and non-reentrant |
optimisticValue | Returns the immediate UI value |
getValueFromState | Extracts the relevant value from state |
applyValueToState | Applies a value back to state |
sendCommandToServer | Executes the server operation |
When the server returns data that should update the state:
void addTodo(Todo newTodo) => optimisticCommand(
key: (AddTodo, newTodo.id),
optimisticValue: () => state.todoList.add(newTodo),
getValueFromState: (state) => state.todoList,
applyValueToState: (state, value) =>
state.copyWith(todoList: value as List<Todo>),
sendCommandToServer: (optimisticValue) async {
final savedTodo = await api.saveTodo(newTodo);
return savedTodo; // Return server response
},
// Replace client-side todo with server-side todo
applyServerResponseToState: (state, serverResponse) {
final savedTodo = serverResponse as Todo;
return state.copyWith(
todoList: state.todoList
.where((t) => t.id != newTodo.id)
.toList()
..add(savedTodo),
);
},
);
Reload data after success or failure:
void deleteTodo(String todoId) => optimisticCommand(
key: (DeleteTodo, todoId),
optimisticValue: () =>
state.todoList.where((t) => t.id != todoId).toList(),
getValueFromState: (state) => state.todoList,
applyValueToState: (state, value) =>
state.copyWith(todoList: value as List<Todo>),
sendCommandToServer: (optimisticValue) async {
await api.deleteTodo(todoId);
return null;
},
// Reload after operation
reloadFromServer: () async {
return await api.loadTodoList();
},
// Control when to reload
shouldReload: ({
required currentValue,
required lastAppliedValue,
required optimisticValue,
required rollbackValue,
required error,
}) => error != null, // Only reload on failure
);
Customize how rollback works:
void addTodo(Todo newTodo) => optimisticCommand(
key: (AddTodo, newTodo.id),
// ... required params ...
// Control whether to rollback
shouldRollback: ({
required currentValue,
required initialValue,
required optimisticValue,
required error,
}) => true, // Default: always rollback on error
// Custom rollback state (e.g., show failed status instead of removing)
rollbackState: ({
required state,
required initialValue,
required optimisticValue,
required error,
}) => state.copyWith(
todoList: state.todoList.map((t) =>
t.id == newTodo.id ? t.copyWith(status: TodoStatus.failed) : t
).toList(),
),
);
Track loading state globally but prevent duplicates per item:
void addTodo(Todo newTodo) => optimisticCommand(
key: AddTodo, // isWaiting(AddTodo) shows any add in progress
nonReentrantKey: (AddTodo, newTodo.id), // Prevents duplicate for same todo
// ... rest of params ...
);
// Widget
if (context.isWaiting(AddTodo)) {
// Any add operation is in progress
}
void addItem(Item newItem) => optimisticCommand(
key: (AddItem, newItem.id),
optimisticValue: () => [...state.items, newItem],
getValueFromState: (state) => state.items,
applyValueToState: (state, value) =>
state.copyWith(items: value as List<Item>),
sendCommandToServer: (optimisticValue) async {
await api.createItem(newItem);
return null;
},
);
void deleteItem(String itemId) => optimisticCommand(
key: (DeleteItem, itemId),
optimisticValue: () =>
state.items.where((i) => i.id != itemId).toList(),
getValueFromState: (state) => state.items,
applyValueToState: (state, value) =>
state.copyWith(items: value as List<Item>),
sendCommandToServer: (optimisticValue) async {
await api.deleteItem(itemId);
return null;
},
);
void updateItem(Item updatedItem) => optimisticCommand(
key: (UpdateItem, updatedItem.id),
optimisticValue: () => state.items
.map((i) => i.id == updatedItem.id ? updatedItem : i)
.toList(),
getValueFromState: (state) => state.items,
applyValueToState: (state, value) =>
state.copyWith(items: value as List<Item>),
sendCommandToServer: (optimisticValue) async {
await api.updateItem(updatedItem);
return null;
},
);
Widget build(BuildContext context) {
final isSaving = context.isWaiting((AddTodo, todoId));
return ListTile(
title: Text(todo.title),
trailing: isSaving
? const CircularProgressIndicator()
: const Icon(Icons.check),
);
}
| Scenario | Use |
|---|---|
| Add/Delete/Update item | optimisticCommand |
| Form submission | optimisticCommand |
| Toggle (like/favorite) with fast taps | optimisticSync |
| Slider/rating value | optimisticSync |
| Settings that change rapidly | optimisticSync |
Rule: Use optimisticCommand for discrete actions, optimisticSync for values that may change rapidly.
Ask the user:
applyServerResponseToState)reloadFromServer)rollbackState)