本文へスキップ
Hironobu Iga

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 便利。