-
Notifications
You must be signed in to change notification settings - Fork 653
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prepare changelog and doc for next version
Co-authored-by: Martin Bonnin <martin@mbonnin.net> add a link to the (upcoming) documentation update date Update docs/source/fetching/defer.mdx Co-authored-by: JV <jvajda@apollographql.com> Update CHANGELOG.md Co-authored-by: JV <jvajda@apollographql.com> Update CHANGELOG.md Co-authored-by: JV <jvajda@apollographql.com> Update docs/source/fetching/defer.mdx Co-authored-by: JV <jvajda@apollographql.com> trying to make yaml happy update `@defer` to stage 2 draft and mention empty `hasNext: false` payloads see #4373 updates from apollographql/apollo-client#10079 fix an instance of Client -> Kotlin
- Loading branch information
1 parent
2c81cf7
commit 73f9950
Showing
6 changed files
with
364 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
--- | ||
title: 'Using the @defer directive in Apollo Kotlin' | ||
description: 'Fetch slower schema fields asynchronously' | ||
--- | ||
|
||
> ⚠️ **The `@defer` directive is experimental in Apollo Kotlin and currently a [Stage 2 GraphQL specification draft](https://github.com/graphql/graphql-spec/blob/main/CONTRIBUTING.md#stage-2-draft).** Before it reaches Stage3 acceptance into the GraphQL specification, it could change in backward incompatible ways. If you have feedback on it, please let us know via [GitHub issues](https://github.com/apollographql/apollo-android/issues/new?assignees=&labels=Type%3A+Bug&template=bug_report.md&title=[Defer%20Support]) or in the [Kotlin Slack community](https://slack.kotl.in/). | ||
Beginning with version `3.6.0`, Apollo Kotlin supports [the `@defer` directive](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md), 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 in-line fragment that contains all slow-resolving fields related to friend data: | ||
|
||
```graphql | ||
query PersonQuery($personId: ID!) { | ||
person(id: $personId) { | ||
# Basic fields (fast) | ||
id | ||
firstName | ||
lastName | ||
|
||
# Friends (slow) | ||
... on User @defer { | ||
friends { | ||
id | ||
} | ||
} | ||
# highlight-start | ||
# Friend fields (slower) | ||
... on User @defer { | ||
friends { | ||
id | ||
} | ||
} | ||
# highlight-end | ||
} | ||
} | ||
``` | ||
|
||
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 | ||
apolloClient.query(PersonQuery(personId)).toFlow().collect { | ||
println("Received: $it") | ||
if (it.dataAssertNoErrors.person.onUser == null) { | ||
// Initial payload: basic info only | ||
// ... | ||
} else { | ||
// Subsequent payload: with friends | ||
// ... | ||
} | ||
} | ||
``` | ||
|
||
Will print something like this: | ||
|
||
``` | ||
Received: Person(id=1, firstName=John, lastName=Doe, onUser=null)) | ||
Received: Person(id=1, firstName=John, lastName=Doe, onUser=OnUser(friends=[Friend(id=2), Friend(id=3)])) | ||
``` | ||
|
||
### 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 `distinct()`: | ||
|
||
```kotlin | ||
apolloClient.query(MyQuery()).toFlow() | ||
.distinct() // filter out duplicates | ||
.collect { /* ... */ } | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
--- | ||
title: Data builders (experimental) | ||
--- | ||
|
||
> ⚠️ **Data builders are experimental and subject to change.** If you have feedback on them, please let us know via [GitHub issues](https://github.com/apollographql/apollo-android/issues/new?assignees=&labels=Type%3A+Bug&template=bug_report.md&title=[Test%20Builders]) or in the [Kotlin Slack community](https://slack.kotl.in/). | ||
Apollo Kotlin generates models for your operations and parsers that create instances of these models from your network responses. Sometimes though, during tests or in other occasions, it is useful to instantiate models manually with known values. Doing so is not as straightforward as it may seem, especially when fragments are used. Creating `operationBased` models requires instantiating every fragment as well as choosing an appropriate `__typename` for each composite type. Data builders make this easier by providing builders that match the structure of the Json document. | ||
|
||
__Note: previous versions of Apollo Kotlin used data builders. Data builders are a simpler version of test builders that also plays nicer with custom scalars. If you're looking for the test builders documentation, it's still available [here](./test-builders).__ | ||
|
||
## Enabling data builders | ||
|
||
To enable data, set the `generateDataBuilders` option to `true`: | ||
|
||
```kotlin title="build.gradle[.kts]" | ||
apollo { | ||
// ... | ||
|
||
// Enable data builder generation | ||
generateDataBuilders.set(true) | ||
} | ||
``` | ||
|
||
This generates a builder for each composite type in your schema as well as a helper `Data {}` function for each of your operations. | ||
|
||
## Example usage | ||
|
||
Let's say we're building a test that uses a mocked result of the following query: | ||
|
||
```graphql | ||
query HeroForEpisode($ep: Episode!) { | ||
hero(episode: $ep) { | ||
firstName | ||
lastName | ||
age | ||
|
||
ship { | ||
model | ||
speed | ||
} | ||
|
||
friends { | ||
firstName | ||
lastName | ||
} | ||
|
||
... on Droid { | ||
primaryFunction | ||
} | ||
|
||
... on Human { | ||
height | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Here's how we can use the corresponding data builder for that mocked result: | ||
|
||
```kotlin | ||
@Test | ||
fun test() { | ||
val data = HeroForEpisodeQuery.Data { | ||
// Set values for particular fields of the query | ||
hero = buildHuman { | ||
firstName = "John" | ||
age = 42 | ||
friends = listOf( | ||
buildHuman { | ||
firstName = "Jane" | ||
}, | ||
buildHuman { | ||
lastName = "Doe" | ||
} | ||
) | ||
ship = buildStarship { | ||
model = "X-Wing" | ||
} | ||
} | ||
} | ||
|
||
assertEquals("John", data.hero.firstName) | ||
assertEquals(42, data.hero.age) | ||
} | ||
``` | ||
|
||
In this example, the `hero` field is a `Human` object with specified values for `firstName` and `age`. The values for `lastName`and `height` are automatically populated with mock values. | ||
Similarly, values for the ship's speed, the 1st friend's last name and 2nd friend's first name are automatically populated. | ||
|
||
> You can replace `buildHuman` above with `buildDroid` to create a `Droid` object instead. | ||
## Configuring default field values | ||
|
||
To assign default values to fields, data builders use an implementation of the `FakeResolver` interface. By default, they use an instance of `DefaultFakeResolver`. | ||
|
||
The `DefaultFakeResolver` gives each `String` field the field's name as its default value, and it increments a counter as it assigns default values for `Int` fields. It defines similar default behavior for other types. | ||
|
||
You can create your _own_ `FakeResolver` implementation (optionally delegating to `DefaultTestResolver` for a head start). You then pass this implementation as a parameter to the `Data` function, like so: | ||
|
||
```kotlin {6} | ||
// A TestResolver implementation that assigns -1 to all Int fields | ||
class MyFakeResolver : FakeResolver { | ||
private val delegate = DefaultFakeResolver(__Schema.all) | ||
|
||
override fun resolveLeaf(context: FakeResolverContext): Any { | ||
return when (context.mergedField.type.leafType().name) { | ||
"Int" -> -1 // Always use -1 for Ints | ||
else -> delegate.resolveLeaf(context) | ||
} | ||
} | ||
|
||
override fun resolveListSize(context: FakeResolverContext): Int { | ||
// Delegate to the default behaviour | ||
return delegate.resolveListSize(context) | ||
} | ||
|
||
override fun resolveMaybeNull(context: FakeResolverContext): Boolean { | ||
// Never | ||
return false | ||
} | ||
|
||
override fun resolveTypename(context: FakeResolverContext): String { | ||
// Delegate to the default behaviour | ||
return delegate.resolveTypename(context) | ||
} | ||
} | ||
|
||
@Test | ||
fun test() { | ||
val data = HeroForEpisodeQuery.Data(resolver = myTestResolver) { | ||
hero = buildHuman { | ||
firstName = "John" | ||
} | ||
} | ||
|
||
// Unspecified Int field is -1 | ||
assertEquals(-1, data.hero.age) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.