Iganinのブログ

日頃の開発で学んだ知見を中心に記事を書いています。

【Flutter】GraphQLClientでキャッシュの永続化を行う(DIにRiverpodを使用)

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

Cash 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の初期化を実施、provderをProvderScope内で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便利。