Watching cached data
Update your UI in response to cache changes
Because your normalized cache can deduplicate your GraphQL data (using proper cache IDs), you can use the cache as the source of truth for populating your UI.
When executing an operation, you can use the ApolloCall.watch
method to automatically notify that operation whenever its associated cached data changes:
1apolloClient.query(TodoDetailsQuery())
2 .watch()
3 .collect { response ->
4 // This code now executes on response *and*
5 // every time cached data changes
6 }
Let's look at some examples.
Watching a query
Let's say we're building a collaborative todo list app that executes these two queries:
1# AllTodos.graphql
2
3# Gets all todo items
4query AllTodos {
5 items {
6 id
7 completed
8 title
9 }
10}
1# TodoDetails.graphql
2
3# Gets one todo item's details
4query TodoDetails($id: String!) {
5 item(id: $id) {
6 id
7 completed
8 title
9 content
10 }
11}
The app's main view executes the AllTodos
query to display a list of todo Item
s, which includes each item's title
and completed
status. Here's an example response:
1{
2 "data": {
3 "items": [
4 {
5 "id": "0",
6 "title": "Write code for Apollo Kotlin 3",
7 "completed": true,
8 },
9 {
10 "id": "1",
11 "title": "Write documentation for Apollo Kotlin 3",
12 "completed": false,
13 }
14 ]
15 }
16}
When someone selects a particular item in the UI, the app executes TodoDetails
to display that item's full details. And because this is a collaborative todo list, those details might have changed on the backend since executing AllTodos
(i.e., another user made changes).
In this case, the TodoDetails
query might return something like this:
1{
2 "data": {
3 "item": {
4 "id": "1",
5 "title": "Write documentation for Apollo Kotlin 3",
6 "completed": true,
7 "content": "Don't forget docs about the normalized cache!"
8 }
9 }
10}
With a normalized cache and properly configured cache IDs, this updated Item
is automatically merged with the existing cached Item
that was returned from AllTodos
. However, our UI doesn't automatically update to reflect the new cached data.
To handle this, we can call watch()
when we execute any query:
1apolloClient.query(TodoDetailsQuery())
2 .watch()
3 .collect { response ->
4 // This code now executes on response *and*
5 // every time cached data changes
6 }
Now if another operation updates this query's cached fields, our UI renders with the same logic it used when the query first returned.
Updating the cache after a mutation
You sometimes need to update your cached data to reflect changes made by a mutation.
For example, in our todo list app, the user might check off a completed item. This might execute the following mutation to modify an Item
's completed
field:
1mutation SetTodoCompleted($id: String!, $completed: Boolean!) {
2 setTodocompleted(id: $id, completed: $completed) { # Returns the modified Item
3 id
4 }
5}
As written, this mutation updates the completed
field on the server, but not in the cache. This is because the mutation's response doesn't include the Item
's completed
field.
To update the cache automatically with the new value, you need to request all modified fields in the response:
1mutation SetTodoCompleted($id: String!, $completed: Boolean!) {
2 setTodoCompleted(id: $id, completed: $completed) {
3 id
4 # Ask for `completed` to update the cache
5 completed
6 }
7}
Because the setTodoCompleted
field above returns an Item
type with both an id
and the completed
field, Apollo Kotlin can update the cached Item
entry with the new data. Additionally, any watchers listening to this Item
's completed
field are automatically notified.
Optimistic updates
It's often possible to predict the most likely result of a mutation before your GraphQL server returns it. Apollo Kotlin can use this "most likely result" to update your UI optimistically, making your app feel more responsive to the user.
You provide optimistic data with the optimisticUpdates
method, like so:
1apolloClient.mutation(SetTodocompletedMutation(id = "1", completed = true))
2 .optimisticUpdates(
3 SetTodocompletedMutation.Data(
4 id = "1",
5 completed = true,
6 )
7 )
8 .execute()
This optimistic data is written to the cache immediately, and any watchers of the corresponding data are notified. Then when the operation returns, the cache is updated again with any changes, and watchers are notified again. If the optimistic data is correct, the second cache update is invisible to the user, because no data changes.