关于 riverpod-offline
这项 Riverpod 技能介绍如何将基于 Notifier 的 Provider 状态保存在设备上,以便应用重启后恢复并支持离线使用。内容涵盖存储配置、序列化、缓存期限、密钥失效和内存存储测试。
主要功能
- 01持久化 Notifier Provider 状态
- 02配置存储、密钥和编解码器
- 03使用 JsonPersist 生成持久化代码
- 04设置缓存期限并丢弃旧数据
- 05使用内存存储进行测试
适用场景
- 01在应用重启后保留 Provider 状态
- 02无需网络请求即可恢复状态
- 03数据结构变更后重置持久化数据
- 04无需数据库即可测试持久化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ serverpod/ skills-registry --skill riverpod-offline安装后在 Claude Code 中输入/riverpod-offline确认已加载。
详细介绍
Riverpod — 离线持久化(实验性)
说明
离线持久化会将提供程序状态存储在设备上,使其在重启后仍然保留,并支持离线使用。Riverpod 不依赖特定存储方式;riverpod_sqflite 等软件包提供 Storage 实现。只有基于 Notifier 的提供程序可以持久化。此功能仍处于实验阶段。
创建 Storage
安装一个软件包(例如 riverpod_sqflite 和 sqflite),然后创建 Storage。使用 SQFlite 时:
final storageProvider = FutureProvider<Storage<String, String>>((ref) async {
return JsonSqFliteStorage.open(
join(await getDatabasesPath(), 'riverpod.db'),
);
});
持久化 Notifier
在 Notifier 的 build 中,调用 persist,并提供:Storage(例如 ref.watch(storageProvider.future))、唯一的 key,以及用于状态的 encode/decode。不要等待 persist;Riverpod 会处理。
class TodoList extends AsyncNotifier<List<Todo>> {
@override
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();
}
}
键
- 在所有已持久化的提供程序之间必须唯一(相同的键会对应同一行,存在数据损坏风险)。
- 在重启后必须保持稳定(更改键会导致无法恢复状态)。
- 对于 family 提供程序,请将参数包含在键中。
JsonPersist(代码生成)
使用 riverpod_sqflite 和代码生成时,可使用 @JsonPersist() 生成 key/encode/decode:
@riverpod
@JsonPersist()
class TodoList extends _$TodoList {
@override
Future<List<Todo>> build() async {
persist(ref.watch(storageProvider.future));
return fetchTodosFromServer();
}
}
缓存时长
默认情况下,状态只会缓存一段较短的时间(例如 2 天)。对于长期保存的数据(例如用户偏好设置),请设置 StorageOptions:
persist(
ref.watch(storageProvider.future),
options: const StorageOptions(cacheTime: StorageCacheTime.unsafe_forever),
// ...
);
如果选择永久保存,请规划好在应用发生变化时删除或迁移数据;Riverpod 不会执行迁移。
Destroy key(简单迁移)
数据结构发生变化时,请使用 destroyKey 丢弃旧数据:
options: const StorageOptions(destroyKey: '1.0'),
在新版本中更新该字符串;此后旧的持久化状态将被忽略,提供程序会从头开始。
等待解码
若要从持久化状态而非网络调用进行初始化,请等待 persist 返回的 future:
await persist(ref.watch(storageProvider.future), key: 'todo_list', ...).future;
return state.value ?? <Todo>[];
测试
将 Storage 提供程序替换为 Storage.inMemory(),这样测试就不需要真实数据库:
ProviderScope(
overrides: [
storageProvider.overrideWith((ref) => Storage<String, String>.inMemory()),
],
child: const MyApp(),
)
对于高级迁移或自定义存储策略,你可能仍需直接操作数据库。