Using the @defer directive in Apollo Kotlin
Fetch slower schema fields asynchronously
@defer
directive is currently experimental in Apollo Kotlin. If you have feedback on it, please let us know via GitHub issues or in the Kotlin Slack community.Apollo Kotlin provides experimental support of the @defer
directive, which 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 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:
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.
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:
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)]))
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.
Limitations/known issues
@defer
cannot be used withresponseBased
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()
:
1apolloClient.query(MyQuery()).toFlow()
2 .distinctUntilChangedBy { it.data } // filter out duplicates
3 .collect { /* ... */ }