riverpod-offline

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Riverpod — Offline persistence (experimental)

Riverpod — 离线持久化(实验性)

Instructions

说明

Offline persistence stores provider state on device so it survives restarts and works offline. Riverpod is storage-agnostic; packages like riverpod_sqflite provide a Storage implementation. Only Notifier-based providers can be persisted. The feature is experimental.
离线持久化会将provider状态存储在设备上,使其在应用重启后依然保留,且可离线使用。Riverpod与存储方式无关;像riverpod_sqflite这样的包提供了Storage实现。仅基于Notifier的provider可以被持久化,该功能目前为实验性。

Creating a Storage

创建Storage

Install a package (e.g. riverpod_sqflite + sqflite) and create a Storage. With SQFlite:
dart
final storageProvider = FutureProvider<Storage<String, String>>((ref) async {
  return JsonSqFliteStorage.open(
    join(await getDatabasesPath(), 'riverpod.db'),
  );
});
安装相关包(例如riverpod_sqflite + sqflite)并创建Storage。使用SQFlite的示例:
dart
final storageProvider = FutureProvider<Storage<String, String>>((ref) async {
  return JsonSqFliteStorage.open(
    join(await getDatabasesPath(), 'riverpod.db'),
  );
});

Persisting a notifier

持久化notifier

Inside the notifier's
build
, call persist with: the Storage (e.g.
ref.watch(storageProvider.future)
), a unique key, and encode/ decode for your state. Do not await persist; Riverpod handles it.
dart
class TodoList extends AsyncNotifier<List<Todo>> {
  
  Future<List<Todo>> build() async {
    persist(
      ref.watch(storageProvider.future),
      key: 'todo_list',
      encode: (todos) => todos.map((todo) => {'task': todo.task}).toList(),
      decode: (json) => (json as List).map((todo) => Todo(task: todo['task'] as String)).toList(),
    );
    return fetchTodosFromServer();
  }
}
在notifier的
build
方法中,调用persist并传入:Storage(例如
ref.watch(storageProvider.future)
)、唯一的key,以及状态的encode/decode方法。无需等待persist;Riverpod会自行处理。
dart
class TodoList extends AsyncNotifier<List<Todo>> {
  
  Future<List<Todo>> build() async {
    persist(
      ref.watch(storageProvider.future),
      key: 'todo_list',
      encode: (todos) => todos.map((todo) => {'task': todo.task}).toList(),
      decode: (json) => (json as List).map((todo) => Todo(task: todo['task'] as String)).toList(),
    );
    return fetchTodosFromServer();
  }
}

Keys

Keys(键)

  • Unique across all persisted providers (same key = same row, risk of corruption).
  • Stable across restarts (changing the key loses restored state).
  • For family providers, include the parameter in the key.
  • 唯一性:所有持久化的provider必须使用唯一key(相同key对应同一存储行,存在数据损坏风险)。
  • 稳定性:跨应用重启保持不变(修改key会导致已恢复的状态丢失)。
  • 对于family provider,需将参数包含在key中。

JsonPersist (code generation)

JsonPersist(代码生成)

With riverpod_sqflite and codegen, use @JsonPersist() so key/encode/decode are generated:
dart

()
class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    persist(ref.watch(storageProvider.future));
    return fetchTodosFromServer();
  }
}
结合riverpod_sqflite和代码生成,使用**@JsonPersist()**注解自动生成key/encode/decode:
dart

()
class TodoList extends _$TodoList {
  
  Future<List<Todo>> build() async {
    persist(ref.watch(storageProvider.future));
    return fetchTodosFromServer();
  }
}

Cache duration

缓存时长

By default state is cached for a short time (e.g. 2 days). For long-lived data (e.g. user preferences), set StorageOptions:
dart
persist(
  ref.watch(storageProvider.future),
  options: const StorageOptions(cacheTime: StorageCacheTime.unsafe_forever),
  // ...
);
If using forever, plan to delete or migrate data when the app changes; Riverpod does not do migrations.
默认状态缓存时间较短(例如2天)。对于长期保存的数据(例如用户偏好设置),可设置StorageOptions
dart
persist(
  ref.watch(storageProvider.future),
  options: const StorageOptions(cacheTime: StorageCacheTime.unsafe_forever),
  // ...
);
如果设置为永久缓存,需在应用更新时规划数据删除或迁移;Riverpod不支持自动迁移。

Destroy key (simple migration)

Destroy key(简单迁移)

When the data shape changes, use destroyKey so old data is discarded:
dart
options: const StorageOptions(destroyKey: '1.0'),
Bump the string in new releases; old persisted state is then ignored and the provider starts fresh.
当数据结构发生变化时,使用destroyKey来丢弃旧数据:
dart
options: const StorageOptions(destroyKey: '1.0'),
在新版本中更新该字符串;旧的持久化状态将被忽略,provider会重新初始化。

Waiting for decode

等待解码完成

To initialize from persisted state instead of a network call, await the persist future:
dart
await persist(ref.watch(storageProvider.future), key: 'todo_list', ...).future;
return state.value ?? <Todo>[];
若要从持久化状态初始化而非通过网络请求,需等待persist的future完成:
dart
await persist(ref.watch(storageProvider.future), key: 'todo_list', ...).future;
return state.value ?? <Todo>[];

Testing

测试

Override the storage provider with Storage.inMemory() so tests don't need a real database:
dart
ProviderScope(
  overrides: [
    storageProvider.overrideWith((ref) => Storage<String, String>.inMemory()),
  ],
  child: const MyApp(),
)
For advanced migrations or custom storage strategies, you may still need to work with the database directly.
使用**Storage.inMemory()**覆盖storage provider,这样测试无需依赖真实数据库:
dart
ProviderScope(
  overrides: [
    storageProvider.overrideWith((ref) => Storage<String, String>.inMemory()),
  ],
  child: const MyApp(),
)
对于高级迁移或自定义存储策略,可能仍需直接操作数据库。