You are viewing documentation for a preview version of this software.

Learn about previews.

Using the @defer and @stream directives in Apollo Kotlin

Fetch slower schema fields asynchronously


note
The incremental delivery format used by @defer/@stream isn't yet standardized. Apollo Kotlin supports the format implemented by Apollo Router, which is described in this specification, and the more recent format described in this specification

The @defer directive enables your queries to receive data for specific fields incrementally, instead of receiving all field data at the same time. This is helpful when some fields in a query take much longer to resolve than 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 profile information as soon as it's available, instead of waiting for the friend fields to resolve.

To achieve this, we can apply the @defer directive to an inline 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 {
10      friends {
11        id
12      }
13    }
14  }
15}

In the generated code for this query, the onUser field for the fragment will be nullable. That is because when the initial payload is received from the server, the fields of the fragment are not yet present. A Person will be emitted with only the basic fields filled in.

When the fields of the fragment are available, a new Person will be emitted, this time with the onUser field present and filled with the fields of the fragment.

Kotlin
1apolloClient.query(PersonQuery(personId)).toFlow().collect {
2  println("Received: $it")
3  if (it.dataOrThrow().person.onUser == null) {
4    // Initial payload: basic info only
5    // ...
6  } else {
7    // Subsequent payload: with friends
8    // ...
9  }
10}

Will print something like this:

Text
1Received: Person(id=1, firstName=John, lastName=Doe, onUser=null))
2Received: Person(id=1, firstName=John, lastName=Doe, onUser=OnUser(friends=[Friend(id=2), Friend(id=3)]))

@stream directive support

Similarly to @defer, the @stream directive can be used to receive the first few items of lists in the initial response, while the remaining items arrive later.

Note: this directive is only supported when using the v0.2 protocol:

Kotlin
1apolloClient = ApolloClient.Builder()
2  .networkTransport(
3    HttpNetworkTransport.Builder()
4      .serverUrl("https://...")
5      // Configure the incremental v0.2 protocol
6      .incrementalDeliveryProtocol(IncrementalDeliveryProtocol.V0_2)
7      .build()
8  )
9  .build()

For example this query:

GraphQL
1query FriendsQuery {
2  friends @stream(initialCount: 2) {
3    firstName
4  }
5}

Will print something like this:

Text
1Received: friends=[Person(firstName=John), Person(firstName=Jane)]
2Received: friends=[Person(firstName=John), Person(firstName=Jane), Person(firstName=Michael), Person(firstName=Patricia), Person(firstName=James)]

Error handling

When using @defer, the incremental payloads received from the server may each contain GraphQL errors related to the fields being returned. These errors are accumulated and exposed in the ApolloResponse.errors field.

Fetch errors like network failures can also happen during collection of the flow, and will be exposed in ApolloResponse.exception

See also the error handling section.

Support matrix

ProtocolCompatible serversSupported directives
v0.1Apollo Router, Apollo Server@defer
v0.2Apollo Server@defer, @stream

Limitations/known issues

  • @defer cannot be used with responseBased codegen.

  • Some servers might send an empty payload to signal the end of the stream. In such a case you will receive an extra terminal emission. You can filter it out by using distinctUntilChangedBy():

Kotlin
1apolloClient.query(MyQuery()).toFlow()
2    .distinctUntilChangedBy { it.data }  // filter out duplicates
3    .collect { /* ... */ }
Feedback

Edit on GitHub

Ask Community