.claude/skills/asyncredux-persistence/SKILL.md
Implement local state persistence using Persistor. Covers creating a custom Persistor class, implementing `readState()`, `persistDifference()`, `deleteState()`, using LocalPersist helper, throttling saves, and pausing/resuming persistence with app lifecycle.
npx skillsauth add marcglasberg/async_redux asyncredux-persistenceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
AsyncRedux provides state persistence by passing a persistor object to the Store. This maintains app state on disk, enabling restoration between sessions.
At startup, read any existing state from disk, create default state if none exists, then initialize the store:
var persistor = MyPersistor();
var initialState = await persistor.readState();
if (initialState == null) {
initialState = AppState.initialState();
await persistor.saveInitialState(initialState);
}
var store = Store<AppState>(
initialState: initialState,
persistor: persistor,
);
The Persistor<St> base class defines these methods:
abstract class Persistor<St> {
/// Read persisted state, or return null if none exists
Future<St?> readState();
/// Delete state from disk
Future<void> deleteState();
/// Save state changes. Provides both newState and lastPersistedState
/// so you can compare them and save only the difference.
Future<void> persistDifference({
required St? lastPersistedState,
required St newState
});
/// Convenience method for initial saves
Future<void> saveInitialState(St state) =>
persistDifference(lastPersistedState: null, newState: state);
/// Controls save frequency. Return null to disable throttling.
Duration get throttle => const Duration(seconds: 2);
}
Extend the abstract class and implement the required methods:
class MyPersistor extends Persistor<AppState> {
@override
Future<AppState?> readState() async {
// Read state from disk (e.g., from SharedPreferences, file, etc.)
return null;
}
@override
Future<void> deleteState() async {
// Delete state from disk
}
@override
Future<void> persistDifference({
required AppState? lastPersistedState,
required AppState newState,
}) async {
// Save state to disk.
// You can compare lastPersistedState with newState to save only changes.
}
@override
Duration get throttle => const Duration(seconds: 2);
}
The throttle getter controls how often state is saved. All changes within the throttle window are collected and saved in a single call. The default is 2 seconds.
// Save at most every 5 seconds
@override
Duration get throttle => const Duration(seconds: 5);
// Disable throttling (save immediately on every change)
@override
Duration? get throttle => null;
Dispatch PersistAction() to save immediately, bypassing the throttle:
store.dispatch(PersistAction());
Control persistence with these store methods:
store.pausePersistor(); // Pause saving
store.persistAndPausePersistor(); // Save current state, then pause
store.resumePersistor(); // Resume saving
Pause persistence when the app goes to background and resume when it becomes active. Create an AppLifecycleManager widget:
class AppLifecycleManager extends StatefulWidget {
final Widget child;
const AppLifecycleManager({
Key? key,
required this.child,
}) : super(key: key);
@override
_AppLifecycleManagerState createState() => _AppLifecycleManagerState();
}
class _AppLifecycleManagerState extends State<AppLifecycleManager>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState lifecycle) {
store.dispatch(ProcessLifecycleChange_Action(lifecycle));
}
@override
Widget build(BuildContext context) => widget.child;
}
Create an action to handle lifecycle changes:
class ProcessLifecycleChange_Action extends ReduxAction<AppState> {
final AppLifecycleState lifecycle;
ProcessLifecycleChange_Action(this.lifecycle);
@override
Future<AppState?> reduce() async {
if (lifecycle == AppLifecycleState.resumed ||
lifecycle == AppLifecycleState.inactive) {
store.resumePersistor();
} else if (lifecycle == AppLifecycleState.paused ||
lifecycle == AppLifecycleState.detached) {
store.persistAndPausePersistor();
} else {
throw AssertionError(lifecycle);
}
return null;
}
}
Wrap your app with the lifecycle manager:
StoreProvider<AppState>(
store: store,
child: AppLifecycleManager(
child: MaterialApp( ... ),
),
)
The LocalPersist class simplifies disk operations for Android/iOS. It works with simple object structures containing only primitives, lists, and maps.
import 'package:async_redux/local_persist.dart';
// Create instance with a file name
var persist = LocalPersist("myFile");
// Save data
List<Object> simpleObjs = [
'Hello',
42,
true,
[100, 200, {"name": "John"}],
];
await persist.save(simpleObjs);
// Load data
List<Object> loaded = await persist.load();
// Append data
List<Object> moreObjs = ['more', 'data'];
await persist.save(moreObjs, append: true);
// File operations
int length = await persist.length();
bool exists = await persist.exists();
await persist.delete();
// JSON operations for single objects
await persist.saveJson(simpleObj);
Object? simpleObj = await persist.loadJson();
Note: LocalPersist only supports simple objects. For complex nested structures or custom classes, you need to implement serialization yourself (e.g., using JSON encoding with toJson/fromJson methods).
URLs from the documentation:
data-ai
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
data-ai
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
testing
Handle user-facing errors with UserException. Covers throwing UserException from actions, setting up UserExceptionDialog, customizing error dialogs with `onShowUserExceptionDialog`, and using UserExceptionAction for non-interrupting error display.
tools
Implement undo/redo functionality using state observers. Covers recording state history with stateObserver, creating a RecoverStateAction, implementing undo for the full state or partial state, and managing history limits.