Add optimistic sync updates for non-blocking operations where only the final value matters and rapid changes coalesce
This skill adds optimisticSync for non-blocking operations where only the final value matters and rapid changes coalesce.
Implements optimistic updates for continuous/rapid operations like:
The UI updates immediately on every interaction, but only the final value is synced to the server.
Use optimisticSync when:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class ItemCubit extends Cubit<ItemState> {
ItemCubit() : super(const ItemState());
void toggleLike(String itemId) => optimisticSync<bool>(
// Unique key per item
key: ('toggleLike', itemId),
// 1. Return the value to apply (toggle current)
valueToApply: () => !state.items[itemId]!.isLiked,
// 2. Apply optimistic value to state
applyOptimisticValueToState: (state, isLiked) => state.copyWith(
items: {
...state.items,
itemId: state.items[itemId]!.copyWith(isLiked: isLiked),
},
),
// 3. Extract value from state (for follow-up detection)
getValueFromState: (state) => state.items[itemId]!.isLiked,
// 4. Send value to server
sendValueToServer: (isLiked) async {
await api.setLiked(itemId, isLiked);
return null;
},
);
}
User rapidly toggles: ON → OFF → ON → OFF → ON
↓
UI shows each change immediately
↓
Request 1 sends: ON (locked)
↓
User toggles to OFF, ON while Request 1 in flight
↓
Request 1 completes, state is now ON
↓
Follow-up sends: ON (current value)
↓
No more changes → lock released
Result: Only 2 requests instead of 5, UI always responsive
| Parameter | Purpose |
|-----------|---------|
| key | Unique identifier per item/operation |
| valueToApply | Returns the value to apply optimistically |
| applyOptimisticValueToState | Applies value to state |
| getValueFromState | Extracts current value (for follow-up detection) |
| sendValueToServer | Sends value to server |
When the server returns data that should update the state:
void toggleLike(String itemId) => optimisticSync<bool>(
key: ('toggleLike', itemId),
valueToApply: () => !state.items[itemId]!.isLiked,
applyOptimisticValueToState: (state, isLiked) => state.copyWith(...),
getValueFromState: (state) => state.items[itemId]!.isLiked,
sendValueToServer: (isLiked) async {
final serverValue = await api.setLiked(itemId, isLiked);
return serverValue; // Return server confirmation
},
// Apply server-confirmed value when stable
applyServerResponseToState: (state, serverResponse) {
final serverLiked = serverResponse as bool;
return state.copyWith(
items: {
...state.items,
itemId: state.items[itemId]!.copyWith(isLiked: serverLiked),
},
);
},
);
The onFinish callback is called when sync completes:
onFinish executes (allowing new syncs to start)void toggleLike(String itemId) => optimisticSync<bool>(
key: ('toggleLike', itemId),
// ... required params ...
// Called when sync completes (success or failure)
onFinish: (optimisticValue, error) async {
if (error != null) {
// Reload from server on error
final item = await api.getItem(itemId);
return state.copyWith(
items: {...state.items, itemId: item},
);
}
return null; // No state change needed on success
},
);
void toggleLike(String itemId) => optimisticSync<bool>(
key: ('toggleLike', itemId),
valueToApply: () => !state.items[itemId]!.isLiked,
applyOptimisticValueToState: (state, isLiked) => state.copyWith(
items: state.items.map((id, item) => MapEntry(
id,
id == itemId ? item.copyWith(isLiked: isLiked) : item,
)),
),
getValueFromState: (state) => state.items[itemId]!.isLiked,
sendValueToServer: (isLiked) async {
await api.setLiked(itemId, isLiked);
return null;
},
);
void toggleSetting(String settingKey) => optimisticSync<bool>(
key: ('setting', settingKey),
valueToApply: () => !state.settings[settingKey]!,
applyOptimisticValueToState: (state, value) => state.copyWith(
settings: {...state.settings, settingKey: value},
),
getValueFromState: (state) => state.settings[settingKey]!,
sendValueToServer: (value) async {
await api.updateSetting(settingKey, value);
return null;
},
);
void setRating(String itemId, int rating) => optimisticSync<int>(
key: ('rating', itemId),
valueToApply: () => rating,
applyOptimisticValueToState: (state, rating) => state.copyWith(
items: {
...state.items,
itemId: state.items[itemId]!.copyWith(rating: rating),
},
),
getValueFromState: (state) => state.items[itemId]!.rating,
sendValueToServer: (rating) async {
await api.setRating(itemId, rating);
return null;
},
);
void incrementCounter(String counterId, int delta) => optimisticSync<int>(
key: ('counter', counterId),
valueToApply: () => state.counters[counterId]! + delta,
applyOptimisticValueToState: (state, value) => state.copyWith(
counters: {...state.counters, counterId: value},
),
getValueFromState: (state) => state.counters[counterId]!,
sendValueToServer: (value) async {
await api.setCounter(counterId, value);
return null;
},
);
class LikeButton extends StatelessWidget {
final String itemId;
@override
Widget build(BuildContext context) {
final item = context.watch<ItemCubit>().state.items[itemId]!;
return IconButton(
icon: Icon(
item.isLiked ? Icons.favorite : Icons.favorite_border,
color: item.isLiked ? Colors.red : null,
),
onPressed: () => context.read<ItemCubit>().toggleLike(itemId),
);
}
}
| Approach | Behavior | Best For |
|----------|----------|----------|
| optimisticSync | Immediate UI + coalesced requests | Rapid toggles, sliders |
| optimisticCommand | Immediate UI + single request | Add/delete/submit |
| debounce | Waits for inactivity | Search input |
| nonReentrant | Drops duplicates | Load data |
Good candidates:
Use optimisticCommand instead for:
Ask the user:
applyServerResponseToState)onFinish to reload on error)npx skills add marcglasberg/add-optimistic-sync下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer