With graphql_flutter, which FetchPolicy does a Query widget use by default, and when would you choose each of the other policies?
answer
- widget watches, client.query does not
- Query widget: cacheAndNetwork
- client.query: cacheFirst
- networkOnly saves, noCache does not
- DefaultPolicies on the client
basics
~20 sA 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.
solid answer
~40 sThe `Query` widget and `useQuery` hook turn their options into a watched query, so they take `client.defaultPolicies.watchQuery`, whose fetch policy is `FetchPolicy.cacheAndNetwork`: cached data renders at once and the network result follows. A one-shot `client.query` defaults to `cacheFirst`, which returns cached data and never refreshes it while it is there. Mutations and subscriptions default to `networkOnly`. I pick `cacheFirst` for data that rarely changes, `networkOnly` when the screen must show server truth but should still refresh the cache, `noCache` when the result should not touch the cache at all, and `cacheOnly` for offline reads that fail rather than hit the network. The policy goes in `QueryOptions(fetchPolicy: ...)`, or app-wide through `GraphQLClient(defaultPolicies: DefaultPolicies(...))`.
code
dart · 20 linesfinal bookDetails = gql(r'''
query Book($id: ID!) { book(id: $id) { id title author coverUrl } }
''');
Widget build(BuildContext context) {
return Query(
options: QueryOptions(
document: bookDetails,
variables: {'id': bookId},
fetchPolicy: FetchPolicy.cacheFirst,
),
builder: (result, {refetch, fetchMore}) {
if (result.hasException) return Text(result.exception.toString());
if (result.isLoading && result.data == null) {
return const CircularProgressIndicator();
}
return BookHeader(data: result.data!['book'] as Map<String, dynamic>);
},
);
}go deeper
Recall the five FetchPolicy values and that a Query widget shows cached data first and then refreshes from the network.
Explain why the Query widget defaults to cacheAndNetwork while client.query defaults to cacheFirst, and how to override per operation or per client.
Trace stale-screen reports to the policy in use, and choose policies per screen by how fresh the data must be and what it costs to fetch.
Set freshness expectations per data type with product and backend teams, so client policies and server caching agree.
## What a fetch policy decides A **`FetchPolicy`** tells `graphql_flutter` (through the `graphql` package) where a result may come from and whether it is written back to the cache. There are five values: | Policy | Reads cache | Goes to network | Writes result to cache | |---|---|---|---| | `cacheFirst` | yes, and stops there if data is found | only on a cache miss | yes | | `cacheAndNetwork` | yes, emits it first | always | yes | | `networkOnly` | no | always; fails if the call fails | yes | | `noCache` | no | always; fails if the call fails | **no** | | `cacheOnly` | yes | never; fails if data is missing | not applicable | ## The defaults, and why the widget differs The defaults live in `DefaultPolicies` on the client and depend on **how** the operation runs: - **`watchQuery`** (used by the `Query` widget and the `useQuery` hook): **`cacheAndNetwork`**; - **`query`** (a one-shot `client.query(...)` call): **`cacheFirst`**; - **`mutate`** and **`watchMutation`** (the `Mutation` widget): **`networkOnly`**; - **`subscribe`**: **`networkOnly`**; `cacheOnly` is invalid for subscriptions. The `Query` widget converts its `QueryOptions` into watch-query options and fills any unset policy from `defaultPolicies.watchQuery`. That is why the same query document behaves differently in a widget and in a repository calling `client.query`: the widget always refreshes, the one-shot call is satisfied by whatever is cached. ## Choosing a policy in a book-club app 1. **Book details** (title, author, cover) change rarely: `cacheFirst` avoids a request each time the page opens. 2. **Review list** on a book page: the default `cacheAndNetwork` shows cached reviews instantly and then the fresh list, so members see new reviews without waiting on a spinner. 3. **Pull-to-refresh**: call the `refetch` function the `Query` builder receives; to guarantee a server round trip from a repository, use `networkOnly`. 4. **A one-off moderation report** that should not linger on the device: `noCache`. 5. **Offline mode**: `cacheOnly` renders what was persisted and fails fast instead of waiting on a dead network. ## Stale reads explained by policy Most "the screen shows old data" reports trace back to one of two causes: - a repository using **`client.query`** with its default `cacheFirst`, so once a result is cached it is served forever; either choose `networkOnly` there or refetch explicitly; - a **`noCache`** query whose result never reaches the cache, so other widgets watching the same data are not updated by it. ## Setting policies - **Per operation**: `QueryOptions(document: ..., fetchPolicy: FetchPolicy.cacheFirst)`; also `errorPolicy` and `cacheRereadPolicy` in the same options. - **Per client**: `GraphQLClient(link: ..., cache: ..., defaultPolicies: DefaultPolicies(query: Policies(fetch: FetchPolicy.networkOnly)))`. Unset fields keep their built-in defaults, because each `Policies` value is merged over them. - **Polling**: `QueryOptions(pollInterval: ...)` re-runs a watched query on a timer, useful for a list that should refresh while visible. ## Error policy sits next to fetch policy The same options carry an **`errorPolicy`**, which decides what happens when the server returns GraphQL errors alongside data: - **`ErrorPolicy.none`** (the default for every operation type): errors are treated like network errors and any returned data is ignored; - **`ErrorPolicy.ignore`**: the data is used and the errors are not reported to the UI; - **`ErrorPolicy.all`**: both data and errors are kept, so a screen can show what it has and a warning. For a review list where one review's author failed to resolve, `all` lets the page render the other reviews instead of an error screen. Choosing it means the builder must check `result.hasException` and `result.data` independently. What a normalised cache is and why partial reads miss are GraphQL-client concepts shared across ecosystems; the Flutter-specific part is which default each API picks and where to override it.
- What is the difference between networkOnly and noCache?Both always go to the network and fail if the request fails. `networkOnly` writes the result into the cache, so other watchers of the same data update; `noCache` leaves the cache untouched, so the result is visible only to the caller.
- How do you change the default for every Query widget in the app?Pass `defaultPolicies: DefaultPolicies(watchQuery: Policies(fetch: ...))` to the `GraphQLClient`. The `Query` widget and `useQuery` hook fill unset options from `defaultPolicies.watchQuery`, and fields you leave out keep their built-in values.
saying these in an interview costs you the question
- A Query widget and client.query share the same cacheFirst default
- cacheFirst refreshes cached data in the background
- noCache still writes the network result into the cache
- Mutations read from the cache by default before hitting the network
- cacheOnly falls back to the network when the cache is empty