-
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>
- Loading branch information
1 parent
efc90e4
commit 9661fe0
Showing
6 changed files
with
344 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,55 @@ | ||
--- | ||
title: @defer support | ||
--- | ||
|
||
> ⚠️ **Defer is a [Stage 1 GraphQL specification proposal](https://github.com/graphql/graphql-spec/blob/main/CONTRIBUTING.md#stage-1-proposal).** Before it reaches acceptance, it might still 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/). | ||
Apollo Kotlin supports [the `@defer` directive](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md), which allows receiving the fields of certain fragments of a query asynchronously. | ||
|
||
For instance consider a service where retrieving basic information from a user is fast, but retrieving the user's friends may take more time. In that case it would make sense to display the basic information in the UI as soon as they arrive, and display a loading indicator in the UI while waiting for the friends. | ||
|
||
Applying the `@defer` directive to the fragment selecting the friends fields indicates that we are ready to receive them later, asynchronously: | ||
|
||
```graphql | ||
query PersonQuery($personId: ID!) { | ||
person(id: $personId) { | ||
# Basic fields (fast) | ||
id | ||
firstName | ||
lastName | ||
|
||
# Friends (slow) | ||
... on User @defer { | ||
friends { | ||
id | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
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)])) | ||
``` | ||
|
||
Note: `@defer` can be used only with the [the `operationBased` codegen](../advanced/response-based-codegen/). |
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
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