skip to content

GraphQL Clients

graphql_flutter wires a GraphQLClient with links and a normalized cache into the widget tree through Query, Mutation and Subscription widgets. Interviewers probe fetch policies and stale cache reads.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a Flutter app using graphql_flutter, how do you build a GraphQLClient and make it available to Query and Mutation widgets?

level: juniorimportance: must knowfreq 55%

answer

  1. links chained, cache attached
  2. AuthLink(getToken:).concat(HttpLink)
  3. GraphQLCache(store: HiveStore())
  4. await initHiveForFlutter() in main
  5. ValueNotifier into GraphQLProvider

basics

~10 s

Chain an AuthLink onto an HttpLink, give GraphQLClient that link and a GraphQLCache (HiveStore for persistence, after await initHiveForFlutter()), wrap it in a ValueNotifier and pass it to a GraphQLProvider above MaterialApp.

solid answer

~40 s

A `GraphQLClient` needs two things: a `link` that sends operations and a `cache`. For the link I build `HttpLink('https://api.example.com/graphql')` and put `AuthLink(getToken: () async => 'Bearer $token')` in front with `authLink.concat(httpLink)`; `getToken` runs for every request and must return the full header value. For the cache, `GraphQLCache()` defaults to an in-memory store; to persist it I call `await initHiveForFlutter()` in `main` and pass `GraphQLCache(store: HiveStore())`. The client goes into a `ValueNotifier<GraphQLClient>` handed to `GraphQLProvider`, which I place above `MaterialApp` so every `Query`, `Mutation` and `Subscription` widget, and the hooks, find it through the context. Replacing `notifier.value`, for example after logout, rebuilds the widgets that depend on it.

code

dart · 23 lines
dart
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

Future<void> main() async {
  await initHiveForFlutter();

  final httpLink = HttpLink('https://api.bookclub.example/graphql');
  final authLink = AuthLink(getToken: () async => 'Bearer ${await tokenStore.read()}');

  final client = ValueNotifier(
    GraphQLClient(
      link: authLink.concat(httpLink),
      cache: GraphQLCache(store: HiveStore()),
    ),
  );

  runApp(
    GraphQLProvider(
      client: client,
      child: const MaterialApp(home: ReadingListScreen()),
    ),
  );
}

go deeper

for a junior

Know the four objects: HttpLink, AuthLink, GraphQLCache and GraphQLClient, and that GraphQLProvider above MaterialApp makes the client available.

for a middle

Explain link composition, why getToken runs per request, the in-memory default store versus HiveStore, and why the provider takes a ValueNotifier.

for a senior

Plan how the client is replaced on logout, how the persisted cache is cleared, and where subscriptions split off to a WebSocketLink.

for a principal

Decide whether a GraphQL client library, plain HTTP with typed models, or code generation best fits the team and the backend contract.

## The pieces `graphql_flutter` is the Flutter layer over the standalone `graphql` package. Setting it up means building three objects and one widget: | Piece | Class | Job | |---|---|---| | transport | `HttpLink` | sends queries and mutations as HTTP requests | | request middleware | `AuthLink` | adds an auth header to each request | | cache | `GraphQLCache` with a `Store` | normalises and keeps results | | client | `GraphQLClient(link:, cache:)` | runs operations, applies policies | | provider | `GraphQLProvider(client: ValueNotifier<GraphQLClient>)` | exposes the client to the widget tree | ## Links A **link** is one step in the pipeline an operation passes through. Links are composed, and the last one must actually send the request (a **terminating link**): - **`HttpLink(uri)`** is the usual terminating link for queries and mutations. - **`AuthLink(getToken: ...)`** is a middleware link. `getToken` is called **for every request**, can be `async`, and must return the **complete header value** including any `Bearer ` prefix; the header name defaults to `Authorization` and can be changed with `headerKey`. - **`authLink.concat(httpLink)`** chains them: auth first, then HTTP. - For subscriptions, a `WebSocketLink` is added with **`Link.split((request) => request.isSubscription, wsLink, httpChain)`**; without the split, subscription operations go to the HTTP link. Because `getToken` runs per request, it can read the current token from secure storage or an auth service; the client does not need rebuilding when a token is refreshed. ## The cache and persistence `GraphQLCache` stores results **normalised** by type and id, so two queries that return the same book share one record. Its `store` decides where that data lives: 1. **Default: `InMemoryStore`**, lost when the app process ends. 2. **`HiveStore`**, backed by Hive boxes on disk, so the last results are available on the next cold start. `HiveStore` needs Hive to be initialised first. In a Flutter app you call **`await initHiveForFlutter()`** in `main` before `runApp`: it ensures the widgets binding exists, points Hive at the app documents directory (on non-web platforms), and opens the default box. Forgetting it is the classic setup failure: the store cannot open its box. ## The provider `GraphQLProvider` takes a **`ValueNotifier<GraphQLClient>`**, not a bare client. It listens to the notifier and exposes the client through an inherited widget, so: - `Query`, `Mutation` and `Subscription` widgets, and the `useQuery`/`useMutation`/`useSubscription` hooks, find the client from their `BuildContext`; - assigning a new client to `notifier.value` (after logout, or when switching environments) rebuilds the dependents with the new client; - `GraphQLProvider.of(context)` returns the notifier for code that needs the client directly, such as a repository calling `client.query`. The package recommends placing the provider **above `MaterialApp`**, so every route pushed by the navigator is inside it. ## Logging out and clearing the cache A persisted cache outlives the session that filled it, so logout needs care, especially on a shared device: - **clear the data**: `client.resetStore(refetchQueries: false)` resets the store (for `HiveStore` that clears its box); the method is marked experimental in the package; - **drop the client**: assign a fresh `GraphQLClient` to the `ValueNotifier` so no widget keeps watching queries that belonged to the previous member; - **stop the token**: make `getToken` return `null` once signed out; `AuthLink` then adds no header rather than an empty one. Skipping these steps is how one member's reading list flashes up for the next person who signs in. ## A book-club app, concretely For a book-club app the client is built once in `main`: the HTTP endpoint for queries and mutations on reviews, an `AuthLink` reading the member's token, a `HiveStore` so the reading list shows instantly on launch, and a `GraphQLProvider` wrapping the whole app. Screens then declare what they need with `Query` widgets or hooks, and never construct a client themselves.

  • Why does GraphQLProvider take a ValueNotifier rather than a GraphQLClient?
    So the client can be swapped at runtime. The provider listens to the notifier and rebuilds its inherited widget when `value` changes, so after logout you can assign a fresh client with an empty cache and every `Query` and `Mutation` below picks it up.
  • What does GraphQLCache use when you pass no store?
    An `InMemoryStore`. Results are normalised and shared across queries while the app runs but are gone after a restart. Pass `HiveStore()`, after `initHiveForFlutter()`, to keep them on disk.

saying these in an interview costs you the question

  • GraphQLCache persists to disk by default
  • AuthLink adds the Bearer prefix for you
  • getToken is called once when the client is created
  • GraphQLProvider can be placed anywhere, even below the screens that query
  • HiveStore works without initialising Hive first
open as a page

With graphql_flutter, which FetchPolicy does a Query widget use by default, and when would you choose each of the other policies?

level: middleimportance: must knowfreq 48%

basics

~20 s

A Query widget watches its query, so it uses the watchQuery default, FetchPolicy.cacheAndNetwork; a one-shot client.query defaults to cacheFirst, and mutations and subscriptions to networkOnly. Override per operation in the options or per client with DefaultPolicies.

open as a page

With graphql_flutter, how do you get typed Dart objects instead of Map<String, dynamic> from query results, and where does code generation fit?

level: middleimportance: should knowfreq 30%

basics

~10 s

Pass a parserFn to QueryOptions<TParsed> and read result.parsedData, which parses once and caches. graphql_codegen, the generator the package points to, emits typed options, variables and hooks from .graphql files, removing hand-written parsing.

open as a page

In a graphql_flutter book-club app, why does a newly posted review not appear in the list after the Mutation succeeds, and how do you fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The cache merges changed fields of entities it already holds, but it cannot know a new review belongs in a cached list. Add it in MutationOptions.update with cache.readQuery and cache.writeQuery, or refetch the list query.

open as a page