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:

Kotlin
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:

GraphQL
1# AllTodos.graphql
2
3# Gets all todo items
4query AllTodos {
5  items {
6    id
7    completed
8    title
9  }
10}
GraphQL
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 Items, which includes each item's title and completed status. Here's an example response:

JSON
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:

JSON
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:

Kotlin
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:

GraphQL
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:

GraphQL
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:

Kotlin
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.

Feedback

Edit on GitHub

Forums