Skip to content

Commit

Permalink
Prepare changelog and doc for next version
Browse files Browse the repository at this point in the history
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
BoD authored and martinbonnin committed Sep 8, 2022
1 parent 2c81cf7 commit 73f9950
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 4 deletions.
133 changes: 133 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,139 @@
Change Log
==========

# Version 3.6.0

_2022-09-01_

This version brings initial support for `@defer` as well as data builders.

## 💙️ External contributors

Many thanks to @engdorm, @Goooler, @pt2121 and @StylianosGakis for their contributions!

## ✨️ [new] `@defer` support

`@defer` support is experimental in the Kotlin Client and currently a [Stage 2 GraphQL specification draft](https://github.com/graphql/graphql-spec/blob/main/CONTRIBUTING.md#stage-2-draft) to allow incremental delivery of response payloads.

`@defer` allows you to specify a fragment as deferrable, meaning it can be omitted in the initial response and delivered as a subsequent payload. This improves latency for all fields that are not in that fragment. You can read more about `@defer` in the [RFC](https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md) and contribute/ask question in the [`@defer` working group](https://github.com/robrichard/defer-stream-wg).

Apollo Kotlin supports `@defer` by default and will deliver the successive payloads as `Flow` items. Given the below query:

```graphql
query GetComputer {
computer {
__typename
id
...ComputerFields @defer
}
}

fragment ComputerFields on Computer {
cpu
year
screen {
resolution
}
}
```

And the following server payloads:

**payload 1**:
```json
{
"data": {
"computer": {
"__typename": "Computer",
"id": "Computer1"
}
},
"hasNext": true
}
```

**payload 2**:
```json
{
"incremental": [
{
"data": {
"cpu": "386",
"year": 1993,
"screen": {
"resolution": "640x480"
}
},
"path": [
"computer",
]
}
],
"hasNext": true
}
```

You can listen to payloads by using `toFlow()`:

```kotlin
apolloClient.query(query).toFlow().collectIndexed { index, response ->
// This will be called twice

if (index == 0) {
// First time without the fragment
assertNull(response.data?.computer?.computerFields)
} else if (index == 1) {
// Second time with the fragment
assertNotNull(response.data?.computer?.computerFields)
}
}
```

You can read more about it in [the documentation](https://www.apollographql.com/docs/kotlin/fetching/defer).

As always, feedback is very welcome. Let us know what you think of the feature by
either [opening an issue on our GitHub repo](https://github.com/apollographql/apollo-android/issues)
, [joining the community](http://community.apollographql.com/new-topic?category=Help&tags=mobile,client)
or [stopping by our channel in the KotlinLang Slack](https://app.slack.com/client/T09229ZC6/C01A6KM1SBZ)(get your
invite [here](https://slack.kotl.in/)).

## ✨️ [new] Data Builders (#4321)

Apollo Kotlin 3.0 introduced [test builders](https://www.apollographql.com/docs/kotlin/testing/test-builders/). While they are working, they have several limitations. The main one was that being [response based](https://www.apollographql.com/docs/kotlin/advanced/response-based-codegen#the-responsebased-codegen), they could generate a lot of code. Also, they required passing custom scalars using their Json encoding, which is cumbersome.

The [data builders](https://www.apollographql.com/docs/kotlin/testing/data-builders/) are a simpler version of the test builders that generate builders based on schema types. This means most of the generated code is shared between all your implementations except for a top level `Data {}` function in each of your operation:

```kotlin
// Replace
val data = GetHeroQuery.Data {
hero = humanHero {
name = "Luke"
}
}

// With
val data = GetHeroQuery.Data {
hero = buildHuman {
name = "Luke"
}
}
```

## ✨️ [new] Kotlin 1.7 (#4314)

Starting with this release, Apollo Kotlin is built with Kotlin 1.7.10. This doesn't impact Android and JVM projects (the minimum supported version of Kotlin continues to be 1.5) but if you are on a project using Native, you will need to update the Kotlin version to 1.7.0+.

## 👷‍ All changes

* Data builders (#4359, #4338, #4331, #4330, #4328, #4323, #4321)
* Add a flag to disable fieldsCanMerge validation on disjoint types (#4342)
* Re-introduce @defer and use new payload format (#4351)
* Multiplatform: add enableCompatibilityMetadataVariant flag (#4329)
* Remove an unnecessary `file.source().buffer()` (#4326)
* Always use String for defaultValue in introspection Json (#4315)
* Update Kotlin dependency to 1.7.10 (#4314)
* Remove schema and AST from the IR (#4303)

# Version 3.5.0

_2022-08-01_
Expand Down
5 changes: 3 additions & 2 deletions docs/source/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"GraphQL variables": "/advanced/operation-variables",
"Error handling": "/essentials/errors",
"Custom scalars": "/essentials/custom-scalars",
"Fragments": "/essentials/fragments"
"Fragments": "/essentials/fragments",
"@defer support (experimental)": "/fetching/defer"
},
"Caching": {
"Introduction": "/caching/introduction",
Expand All @@ -64,7 +65,7 @@
"Overview": "/testing/overview",
"Mocking HTTP responses": "/testing/mocking-http-responses",
"Mocking GraphQL responses": "/testing/mocking-graphql-responses",
"Test builders": "/testing/test-builders",
"Data builders": "/testing/data-builders",
"UI Tests": "/testing/ui-tests"
},
"Advanced": {
Expand Down
73 changes: 73 additions & 0 deletions docs/source/fetching/defer.mdx
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 { /* ... */ }
```

139 changes: 139 additions & 0 deletions docs/source/testing/data-builders.mdx
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)
}
```
2 changes: 1 addition & 1 deletion docs/source/testing/test-builders.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Test builders (experimental)
---

> ⚠️ **Test 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/).
> ⚠️ Test builders are [response based](https://www.apollographql.com/docs/kotlin/advanced/response-based-codegen#the-responsebased-codegen) and may generate a lot of code. Moving forward, we recommend to use [data builders](./data-builders) instead, which are simpler to use. This page is kept as reference only.
Apollo Kotlin provides **test builders** that enable you to instantiate your GraphQL model classes with default values. Test builders are especially helpful for testing models with a large number of fields or a deeply nested hierarchy. They automatically populate the `__typename` field and deduplicate merged fields whenever possible.

Expand Down
Loading

0 comments on commit 73f9950

Please sign in to comment.