Using the @defer directive in Apollo iOS


⚠️ The @defer directive is currently an experimental feature in Apollo iOS and is available for use from version 1.14.0. If you have feedback on the feature, please let us know via a new GitHub issue or search through any existing @defer-related issues.

The @defer directive enables your queries to receive data for specific fields asynchronously. This is helpful whenever some fields in a query take much longer to resolve than the others.

For example, let's say we're building a social media application that can quickly fetch a user's basic profile information, but retrieving that user's friends takes longer. If we include all of those fields in a single query we want to be able to display the basic profile information as soon as it's available instead of waiting for the friends field to resolve.

To achieve this, we can apply the @defer directive to an in-line fragment that contains all slow-resolving fields related to friend data:

GraphQL
1query PersonQuery($personId: ID!) {
2  person(id: $personId) {
3    # Basic fields (fast)
4    id
5    firstName
6    lastName
7
8    # Friend fields (slower)
9    ... on User @defer(label: "deferredFriends") {
10      friends {
11        id
12      }
13    }
14  }
15}

In the generated code for this query the asUser type case will be optional, it will also have a fragments container in which there will be an optional fragment with the same name as the label property value used in the query. The deferred fragment is optional because it will not be returned with the basic fields, it will be returned in a separate response instead.

All deferred fragments are annotated with the @Deferred property wrapper. This allows you to check the state of any deferred fragment through it's projected value. When the fields of the deferred fragment are received, another response will be returned with all basic fields and friends fields too.

Swift
1client.fetch(query: ExampleQuery()) { result in
2  switch (result) {
3  case let .success(data):
4    if case .fulfilled = data.data?.person.asUser?.fragments.$deferredFriends {
5      print("Query Success! Received all fields.")
6    } else {
7      print("Query Success! Received basic fields only.")
8    }
9  case let .failure(error):
10    print("Query Failure! \(error)")
11  }
12}

Will print something like this:

Text
1Query Success! Received basic fields only.
2Query Success! Received all fields.

Requirements

  • The @defer directive must be used on a type case.

  • The @defer directive must have a label property.

Caching

Reading cache data

Typical caching behaviour and functionality is supported for operations with the @defer directive too. Cache reads behave slightly differently to network requests in that all cached data, including any cached deferred fragments, will be returned in the first response. In other words, if the entire query has been cached then there will only be one response with all basic and deferred fragments. If you manually added only the basic data to the cache then you would receive only the basic data in the first response and depending on the cache policy used may get the deferred fragments returned from the network separately.

Writing cache data

We're still building Selection Set Initializer compatibility for deferred fragments. Until that is complete, writing to the cache requires that the local cache mutation, or named fragment selection set, be initialized using the private .init(_dataDict: DataDict) function.

Feedback

Edit on GitHub

Forums