--- name: asyncredux-persistence description: 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. --- ## Overview AsyncRedux provides state persistence by passing a `persistor` object to the Store. This maintains app state on disk, enabling restoration between sessions. ## Store Initialization with Persistor At startup, read any existing state from disk, create default state if none exists, then initialize the store: ```dart var persistor = MyPersistor(); var initialState = await persistor.readState(); if (initialState == null) { initialState = AppState.initialState(); await persistor.saveInitialState(initialState); } var store = Store( initialState: initialState, persistor: persistor, ); ``` ## The Persistor Abstract Class The `Persistor` base class defines these methods: ```dart abstract class Persistor { /// Read persisted state, or return null if none exists Future readState(); /// Delete state from disk Future deleteState(); /// Save state changes. Provides both newState and lastPersistedState /// so you can compare them and save only the difference. Future persistDifference({ required St? lastPersistedState, required St newState }); /// Convenience method for initial saves Future saveInitialState(St state) => persistDifference(lastPersistedState: null, newState: state); /// Controls save frequency. Return null to disable throttling. Duration get throttle => const Duration(seconds: 2); } ``` ## Creating a Custom Persistor Extend the abstract class and implement the required methods: ```dart class MyPersistor extends Persistor { @override Future readState() async { // Read state from disk (e.g., from SharedPreferences, file, etc.) return null; } @override Future deleteState() async { // Delete state from disk } @override Future 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); } ``` ## Throttling 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. ```dart // 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; ``` ## Forcing Immediate Save Dispatch `PersistAction()` to save immediately, bypassing the throttle: ```dart store.dispatch(PersistAction()); ``` ## Pausing and Resuming Persistence Control persistence with these store methods: ```dart store.pausePersistor(); // Pause saving store.persistAndPausePersistor(); // Save current state, then pause store.resumePersistor(); // Resume saving ``` ## App Lifecycle Integration Pause persistence when the app goes to background and resume when it becomes active. Create an `AppLifecycleManager` widget: ```dart 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 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: ```dart class ProcessLifecycleChange_Action extends ReduxAction { final AppLifecycleState lifecycle; ProcessLifecycleChange_Action(this.lifecycle); @override Future 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: ```dart StoreProvider( store: store, child: AppLifecycleManager( child: MaterialApp( ... ), ), ) ``` ## LocalPersist Helper The `LocalPersist` class simplifies disk operations for Android/iOS. It works with simple object structures containing only primitives, lists, and maps. ```dart import 'package:async_redux/local_persist.dart'; // Create instance with a file name var persist = LocalPersist("myFile"); // Save data List simpleObjs = [ 'Hello', 42, true, [100, 200, {"name": "John"}], ]; await persist.save(simpleObjs); // Load data List loaded = await persist.load(); // Append data List 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). ## References URLs from the documentation: - https://asyncredux.com/sitemap.xml - https://asyncredux.com/flutter/miscellaneous/persistence - https://asyncredux.com/flutter/basics/store - https://asyncredux.com/flutter/miscellaneous/database-and-cloud - https://asyncredux.com/flutter/intro - https://asyncredux.com/flutter/testing/mocking - https://asyncredux.com/flutter/advanced-actions/redux-action - https://asyncredux.com/flutter/about - https://asyncredux.com/flutter/testing/store-tester - https://asyncredux.com/flutter/miscellaneous/advanced-waiting