riverpod-offline について
Riverpod の Notifier ベースの Provider 状態を端末に保存し、アプリの再起動後やオフライン時に利用する方法を扱うスキルです。ストレージ設定、シリアライズ、キャッシュ期間、キーによるデータ破棄、メモリ内ストレージでのテストを説明します。
主な機能
- 01Notifier Provider 状態の永続化
- 02ストレージ、キー、エンコード設定
- 03JsonPersist によるコード生成
- 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 を await しないでください。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() を使用すると、キー/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),
// ...
);
forever を使用する場合は、アプリの変更時にデータを削除または移行する計画を立ててください。Riverpod は移行を行いません。
destroyKey(簡易移行)
データの形式が変わった場合は、古いデータを破棄するために destroyKey を使用します。
options: const StorageOptions(destroyKey: '1.0'),
新しいリリースでは文字列を更新してください。古い永続化状態は無視され、プロバイダーは初期状態から開始します。
デコードの完了を待つ
ネットワーク呼び出しではなく、永続化された状態から初期化するには、persist の future を await します。
await persist(ref.watch(storageProvider.future), key: 'todo_list', ...).future;
return state.value ?? <Todo>[];
テスト
ストレージプロバイダーを Storage.inMemory() でオーバーライドすると、テストで実際のデータベースが不要になります。
ProviderScope(
overrides: [
storageProvider.overrideWith((ref) => Storage<String, String>.inMemory()),
],
child: const MyApp(),
)
高度な移行やカスタムストレージ戦略では、データベースを直接操作する必要がある場合もあります。