Flutter の GraphQLClient でキャッシュの永続化を行う
HiveStore を使って GraphQL クライアントのキャッシュを永続化する方法と、初期化が非同期であることから来る Riverpod 側の面倒をどう避けるかです。
- 公開日
この記事は別サイトにも掲載しています。 https://iganin.hatenablog.com/entry/2021/09/03/102654
TL;DR;
- HiveStore を使う
- HiveStore の初期化は非同期処理のため graphql の provider が Future となってしまいめんどくさい
- 上記解消のために main.dart で初期化を終わらせ ProviderScope で override する
環境
[✓] Flutter (Channel stable, 2.2.3, on macOS 11.3.1 20E241 darwin-x64, locale ja-JP)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2021.1.2)
[✓] VS Code (version 1.59.1)
hooks_riverpod: 0.14.0+4
graphql: 5.0.0
Cache Persistence
GraphqlClient はクライアント内でキャッシュを管理してくれる。(やりようによっては正規化までサポートしている、素晴らしい) デフォルトだとインメモリキャッシュだが、DB を使用した永続化も可能。透過的なキャッシュを実装する上では理想的だと思う。
GraphqlClient でキャッシュを永続化するには HiveStore を使う。
GraphqlFlutter であれば初期化処理は initHiveForFlutter() を main.dart で呼べば済むが、Graphql を使っている場合はもう少し手間がかかる。
一例だが下記のようになる。DB 保存先の Path を取得し、HiveStore を open する。デフォルトでは内部で保持している Box の名称が graphqlClientStore となるが、open の引数に name があるため変更もできる。
GraphQLClient の生成時の cache に GraphQLCache(store: hiveStore) を入れれば OK。
final appDir = await getApplicationDocumentsDirectory();
final path = appDir.path;
final store = await HiveStore.open(path: path);
final client = GraphQLClient(
link: authLink.concat(_baseLink), cache: GraphQLCache(store: store));
Riverpod で DI してる時
Riverpod で DI している時は、graphqlClient も provider 経由で取得したい。 ただ、hiveStore の初期化が非同期なので素直にやると FutureProvider となってしまい、使用する側がとてもめんどくさくなってしまう。SharedPreferences を使用する場合と同様の問題が起きる。
そこで hiveStore を渡すよう用の provider を作成し、main.dart で hiveStore の初期化を実施、provider を ProviderScope 内で override することで非同期処理を吸収してしまう。
final hiveStoreProvider = Provider<HiveStore>((ref) {
throw Exception('Provider was not initialized');
});
Future<void> main() async {
~~~~
var appDir = await getApplicationDocumentsDirectory();
var path = appDir.path;
final store = await HiveStore.open(path: path);
runApp(
ProviderScope(
overrides: [
hiveStoreProvider.overrideWithValue(store),
],
child: App(),
),
);
~~~~
}
上記の対応をすることで、graphqlClient の provider は下記のように同期的に呼び出せるようになる。
final gqlClientProvider = Provider((ref) => AppGqlClient((ref.read)));
最後に
GraphqlClient 便利。