Persisting the cache in Flutter's GraphQLClient
How to persist a GraphQL client's cache with HiveStore, and how to avoid the mess on the Riverpod side that comes from its initialisation being asynchronous.
- Published
This article is also published elsewhere. https://iganin.hatenablog.com/entry/2021/09/03/102654
Originally written in Japanese. This is a translation of the same piece.
TL;DR
- Use
HiveStore - Initialising
HiveStoreis asynchronous, so the graphql provider ends up as aFuture, which is a pain - To get around that, finish the initialisation in
main.dartand override the provider inProviderScope
Environment
[✓] 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 manages the cache for you inside the client. (Set it up right and it even supports
normalisation — excellent.) The default is an in-memory cache, but you can also persist it to a database,
which strikes me as ideal if you want a transparent cache.
To persist the cache in GraphqlClient, use HiveStore. With GraphqlFlutter the initialisation is just a
call to initHiveForFlutter() in main.dart, but with Graphql it takes a bit more work.
Here is one way to do it. Get the path where the database should live, then open the HiveStore. By default
the box it holds internally is named graphqlClientStore, but open takes a name argument, so you can
change it. Pass GraphQLCache(store: hiveStore) as the cache when you create the GraphQLClient and you
are done.
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));
When you are injecting with Riverpod
If you are using Riverpod for DI, you want to get graphqlClient through a provider too. But initialising
hiveStore is asynchronous, so the obvious approach leaves you with a FutureProvider, which is a real
nuisance on the calling side. It is the same problem you hit with SharedPreferences.
So: create a provider whose job is to hand over the hiveStore, do the initialisation in main.dart, and
override the provider inside ProviderScope. That absorbs the asynchronous part.
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(),
),
);
~~~~
}
With that in place, the graphqlClient provider can be called synchronously:
final gqlClientProvider = Provider((ref) => AppGqlClient((ref.read)));
Closing
GraphqlClient is handy.