Skip to content

Commit

Permalink
[docs 📚] next version docs (#5759)
Browse files Browse the repository at this point in the history
* Add note about directive validation

* add doc about Network monitor

* tweak title

* Bump kotlin_labs to v0.3

* add left panel entry

* add experimental websockets doc

* wording

* wording

* Update ApolloCompilerPlugin

* use our own note

* add a section about arguments and limitations

* Remove Espresso IdlingResource from the README as it's deprecated

* add beta.6 changelog

* formating

* typo

---------

Co-authored-by: BoD <BoD@JRAF.org>
  • Loading branch information
martinbonnin and BoD committed Apr 23, 2024
1 parent bb46257 commit 3c5634a
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 12 deletions.
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,75 @@
Change Log
==========

# Version 4.0.0-beta.6

_2024-04-23_


## SQL cache performance improvements

If you're using a chained memory + SQL cache, #5840 makes sure cache writes are wrapped in a transaction, making them much faster.

## Apollo Compiler Plugins

`Plugin` is renamed to `ApolloCompilerPlugin`. There is a new `documentTransform` API as well as other fixes. More details in the [compiler plugins documentation](https://deploy-preview-5759--apollo-android-docs.netlify.app/advanced/compiler-plugins).

## Experimental WebSockets

A new `.websocket` package is available that makes it easier to retry WebSockets and handle errors. More details and migration guide in the [experimental websockets documentation](https://deploy-preview-5759--apollo-android-docs.netlify.app/advanced/experimental-websockets).

## ApolloIdlingResource is deprecated

We recommend using reactive patterns to test your UI instead. See [this article about ways to do so](https://medium.com/androiddevelopers/alternatives-to-idling-resources-in-compose-tests-8ae71f9fc473).

## Removed androidx.startup dependency

androidx.startup was introduced in beta.5 but is problematic for unit tests and other cases. beta.6 removes that dependency. More details in the [network connectivity documentation](https://deploy-preview-5759--apollo-android-docs.netlify.app/advanced/network-connectivity).

## WasmJS support for apollo-adapter

You can see Wasm in action at https://wasm.confetti-app.dev/

Many thanks to @joreilly, @ychescale9 and @japhib for their contributions to this release 💙!

## 👷‍ All changes
* [normalized-cache]: use a single SQL transaction when using MemoryCache chaining (#5840)
* [compiler] expose apollo-ast as an api dependency (#5838)
* [compiler] Rename `Plugin` to `ApolloCompilerPlugin` and add error message for bad configurations (#5821)
* [IJ Plugin] Fix pulling file from device not working on AS Koala (#5822)
* [compiler] Add `@ApolloEnumConstructor` and make enum as sealed class Unknown constructor opt-in (#5813)
* [runtime] Move ApolloParseException to ApolloNetworkException (#5816)
* [normalized-cache] Let isFromCache be about the ApolloResponse (#5805)
* [compiler] Add DocumentTransform API (#5809)
* [idling-resource] Deprecate ApolloIdlingResource (#5817, #5764)
* [runtime] Share the default OkHttpBuilder (#5811)
* [adapters] Support Kotlin/Wasm for apollo-adapters (#5803)
* [all] Bump Kotlin to 2.0.0-RC1 (#5802)
* [Codegen] Add CompiledArgumentDefinition (#5797, #5837)
* [runtime] Merge experimental WebSocketNetworkTransport in apollo-runtime (#5790)
* [normalized-cache] Cache pagination: add FieldNameGenerator and EmbeddedFieldsProvider (#5772)
* [runtime] Support configuring `ApolloClient` with lazily initialized `Call.Factory`. (#5784)
* [runtime] fix ApolloClient.Builder.okHttpClient() returns null instead of this (#5778)
* [normalized-cache] Fix variable coercion in lists. Absent variables are coerced to null (#5773)
* [IJ Plugin] Fix an NPE (#5770)
* [runtime] Simplify ApolloCall (#5765)
* [runtime] remove `androidx.startup` dependency (#5761, #5720)
* [compiler] Bump kotlin_labs definitions to v0.3 (#5762)
* [Pagination] Support nodes in Connection types (#5754)
* [compiler] Directive validation is now enforced by default (#5758)
* [cache] Make ApolloStore.publish() suspend (#5755)
* [runtime] Change the dispatcher earlier in the chain (#4319)
* [IJ Plugin] Add an advanced setting to include generated code references in GraphQL "Go To Declaration" (#5743)
* [IJ Plugin] Fix presentation of Kotlin elements when navigating to them from GraphQL (#5739)
* [IJ Plugin] Consider all Gradle projects recursively (#5734)
* [runtime] Deprecate ApolloClient.Builder.addInterceptors() (#5733)
* [all] use jdk-release (#5731)
* [http-cache] Ignore IOException when calling ApolloHttpCache.remove (#5729)
* [IJ plugin] Bump platformVersion and pluginSinceBuild from 232 to 233 (#5726)
* [runtime] add ApolloClient.failFastIfOffline (#5725)
* [all] Introduce "filesystem" sourceSet and use okio 3.9.0 (#5719)
* [runtime] Do not use Ktor in Js HttpEngine, use fetch directly instead (#5702)

# Version 3.8.3

_2024-03-20_
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ This library is designed primarily with Android in mind, but you can use it in a
* Auto Persisted Queries
* Query batching
* File uploads
* Espresso IdlingResource
* Fake models for tests
* AppSync and graphql-ws websockets
* GraphQL AST parser
Expand Down
43 changes: 36 additions & 7 deletions docs/source/advanced/compiler-plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ Next create your plugin in a `src/main/kotlin/mypackage/MyPlugin` file:
package mypackage

import com.apollographql.apollo3.compiler.OperationOutputGenerator
import com.apollographql.apollo3.compiler.Plugin
import com.apollographql.apollo3.compiler.ApolloCompilerPlugin
import com.apollographql.apollo3.compiler.operationoutput.OperationDescriptor
import com.apollographql.apollo3.compiler.operationoutput.OperationId

class MyPlugin: Plugin {
class MyPlugin: ApolloCompilerPlugin {
override fun operationIds(descriptors: List<OperationDescriptor>): List<OperationId> {
// This assumes the returned ids are in the same order as the descriptors
return registerOperations(descriptors).withIndex().map { OperationId(it.value, descriptors[it.index].name) }
Expand All @@ -56,15 +56,15 @@ class MyPlugin: Plugin {
}
```

Make your plugin discoverable by [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) using a resource in `src/main/resources/META-INF/services/com.apollographql.apollo3.compiler.Plugin`. This file contains the fully qualified name of your plugin:
Make your plugin discoverable by [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) using a resource in `src/main/resources/META-INF/services/com.apollographql.apollo3.compiler.ApolloCompilerPlugin`. This file contains the fully qualified name of your plugin:

```
mypackage.MyPlugin
```

> [!NOTE]
> The name of the resource file is important. It must be `com.apollographql.apollo3.compiler.Plugin` and be in the `META-INF/services` folder. This is how `ServiceLoader` looks up `Plugins` at runtime.
<Note>
The name of the resource file is important. It must be `com.apollographql.apollo3.compiler.ApolloCompilerPlugin` and be in the `META-INF/services` folder. This is how `ServiceLoader` looks up plugins at runtime.
</Note>
# Adding a plugin to the Apollo compiler classpath

Use the `Service.plugin()` Gradle method to add the plugin to the Apollo compiler classpath:
Expand All @@ -88,8 +88,37 @@ apollo {

The plugin code will now be invoked the next time the compiler is invoked.

# Passing arguments to your Apollo compiler plugin

Because the compiler plugin runs in an isolated classpath, you can't use classes or data from your main build logic classpath.

In order to pass build-time arguments to your Apollo compiler plugin, you can use code generation and tools like [gradle-buildconfig-plugin](https://github.com/gmazzo/gradle-buildconfig-plugin):

```kotlin
// my-plugin/build.gradle.kts
plugins {
id("com.github.gmazzo.buildconfig")
}

buildConfig {
useKotlinOutput()
packageName("com.example.myplugin")

buildConfigField("arg1", arg1Value)
buildConfigField("arg2", arg2Value)
// etc...
}
```

# Limitations

Because codegen is run in a separate classloader when using compiler plugins, it's not possible to use `packageNameGenerator`, `operationIdGenerator` or `operationOutputGenerator` at the same time as compiler plugins. If you want to use them, you'll have to:

* use `ApolloCompilerPlugin.layout()` instead of `packageNameGenerator`
* use `ApolloCompilerPlugin.operationIds()` instead of `operationIdGenerator` and `operationOutputGenerator`

# Other references

For other plugin APIs like layout, IR, JavaPoet and KotlinPoet transforms, check out the [Plugin API docs](https://www.apollographql.com/docs/kotlin/kdoc/apollo-compiler/com.apollographql.apollo3.compiler/-plugin/index.html)
For other plugin APIs like layout, IR, JavaPoet and KotlinPoet transforms, check out the [ApolloCompilerPlugin API docs](https://www.apollographql.com/docs/kotlin/kdoc/apollo-compiler/com.apollographql.apollo3.compiler/-apollocompilerplugin/index.html)

For more examples, check out the [integration-tests](https://github.com/apollographql/apollo-kotlin/tree/main/tests/compiler-plugins).
126 changes: 126 additions & 0 deletions docs/source/advanced/experimental-websockets.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Experimental websockets
---

> ⚠️ **Experimental websockets APIs are [experimental](https://www.apollographql.com/docs/resources/release-stages/#experimental-features) in Apollo Kotlin.** If you have feedback on them, please let us know via [GitHub issues](https://github.com/apollographql/apollo-kotlin/issues/new?assignees=&labels=Type%3A+Bug&template=bug_report.md&title=[Defer%20Support]) or in the [Kotlin Slack community](https://slack.kotl.in/).

Historically, WebSockets have been one of the most complex and error-prone parts of Apollo Kotlin because:

1. The WebSocket transport protocol has no official specification and different implementations have different behaviours.
2. WebSockets are stateful and making them work using the old Kotlin native memory model was challenging.
3. Because WebSockets are long-lived connections, they are more exposed to errors and knowing when (or if) to retry is hard.
4. Not all subscriptions happen on WebSockets. Some use [HTTP multipart](https://www.apollographql.com/docs/router/executing-operations/subscription-multipart-protocol/) for an example.

Starting with 4.0.0, Apollo Kotlin provides a new `com.apollographql.apollo3.network.websocket` package containing new `WebSocketNetworkTransport` and `WebSocketEngine` implementations (instead of `com.apollographql.apollo3.network.ws` for the current implementations).

The `com.apollographql.apollo3.network.websocket` implementation provides the following:

1. Defaults to the [graphql-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) protocol, which has become the de facto standard. Using the other protocols is still possible but having a main, specified, protocol ensures we can write a good and solid test suite.
2. Does not inherit from the old memory model design, making the code considerably simpler. In particular, `WebSocketEngine` is now event based and no attempt at flow control is done. If you buffers grow too much, your subscription fails.
3. Plays nicely with the ApolloClient `retryOnError` API.
4. Handles different Subscription transports more consistently.

## Status

While they are `@ApolloExperimental`, we believe the new `.websocket` APIS to be more robust than the non-experimental `.ws` ones. They are safe to use in non-lib use cases.

The "experimental" tag is to account for required API breaking changes based on community feedback. Ideally no change is needed.

After a feedback phase, the current `.ws` APIs will become deprecated and the `.websocket` one promoted to stable by removing the `@ApolloExperimental` annotations.

## Migration guide

In simple cases where you did not configure the underlying `WsProtocol` or retry logic, the migration should be about replacing `com.apollographql.apollo3.network.ws` with `com.apollographql.apollo3.network.websocket` everywhere:

```kotlin
// Replace
import com.apollographql.apollo3.network.ws.WebSocketNetworkTransport
import com.apollographql.apollo3.network.ws.WebSocketEngine
// etc...

// With
import com.apollographql.apollo3.network.websocket.WebSocketNetworkTransport
import com.apollographql.apollo3.network.websocket.WebSocketEngine
// etc...
```

Because we can't remove the current APIs just yet, the `ApolloClient.Builder` shortcut APIs are still pointing to the `.ws` implementations. To use the newer `.websocket` implementation, pass a `websocket.WebSocketNetworkTransport` directly:

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
.serverUrl(serverUrl)
.webSocketServerUrl(webSocketServerUrl)
.webSocketEngine(myWebSocketEngine)
.webSocketIdleTimeoutMillis(10_000)
.build()

// With
import com.apollographql.apollo3.network.websocket.*

// [...]

ApolloClient.Builder()
.serverUrl(serverUrl)
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder()
.serverUrl(webSocketServerUrl)
// If you didn't set a WsProtocol before, make sure to include this
.wsProtocol(SubscriptionWsProtocol())
// If you were already using GraphQLWsProtocol, this is now the default
//.wsProtocol(GraphQLWsProtocol())
.webSocketEngine(myWebSocketEngine)
.idleTimeoutMillis(10_000)
.build()
)
.build()
```

To account for non-websocket transports, like [multipart subscriptions](https://www.apollographql.com/docs/router/executing-operations/subscription-multipart-protocol/), the retry is now handled on the `ApolloClient` instead of the `NetworkTransport`.

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder()
.serverUrl(url)
.reopenWhen { _, _ ->
delay(1000)
true
}
.build()
)

// With
val apolloClient = ApolloClient.Builder()
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder()
.serverUrl(url)
.build()
)
// Only retry subscriptions
.retryOnError { it.operation is Subscription }
```

The above uses the default retry algorithm:

* Wait until the network is available if you configured a [NetworkMonitor](network-connectivity).
* Or use exponential backoff else.

To customize the retry logic more, use `addRetryOnErrorInterceptor`:

```kotlin
val apolloClient = ApolloClient.Builder()
.subscriptionNetworkTransport(
WebSocketNetworkTransport.Builder()
.serverUrl(url)
.build()
)
.addRetryOnErrorInterceptor { apolloRequest, exception, attempt ->
// retry logic here

// return true to retry or false to terminate the Flow
true
}
```
80 changes: 80 additions & 0 deletions docs/source/advanced/network-connectivity.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: Monitor your network state to reduce latency (experimental)
---

> ⚠️ **Network Monitor APIs are [experimental](https://www.apollographql.com/docs/resources/release-stages/#experimental-features) in Apollo Kotlin.** If you have feedback on them, please let us know via [GitHub issues](https://github.com/apollographql/apollo-kotlin/issues/new?assignees=&labels=Type%3A+Bug&template=bug_report.md&title=[Defer%20Support]) or in the [Kotlin Slack community](https://slack.kotl.in/).
>
Android and Apple targets provide APIs to monitor the network state of your device:

- [ConnectivityManager](https://developer.android.com/training/monitoring-device-state/) on Android targets.
- [NWPathMonitor](https://developer.apple.com/documentation/network/nwpathmonitor) on Apple targets.

You can configure your [ApolloClient](https://www.apollographql.com/docs/kotlin/kdoc/apollo-runtime/com.apollographql.apollo3/-apollo-client/index.html) to use these APIs to improve latency of your requests using the `NetworkMonitor` API:

```kotlin
// androidMain
val networkMonitor = NetworkMonitor(context)

// appleMain
val networkMonitor = NetworkMonitor()

// commonMain
val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.networkMonitor(networkMonitor)
.build()
```

### `failFastIfOffline`

When a `NetworkMonitor` is configured, you can use `failFastIfOffline` to avoid trying out request if the device is offline:

```kotlin
// Opt-in `failFastIfOffline` on all queries
val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.failFastIfOffline(true)
.build()

val response = apolloClient.query(myQuery).execute()
println(response.exception?.message)
// "The device is offline"

// Opt-out `failFastIfOffline` on a single query
val response = apolloClient.query(myQuery).failFastIfOffline(false).execute()
```

### `retryOnError`

When a `NetworkMonitor` is configured, `retryOnError` uses `NetworkMonitor.waitForNetwork()` instead of the default exponential backoff algorithm in order to reconnect faster when connectivity is back.

### customizing the retry algorithm

You can customize the retry algorithm further by defining you won interceptor. Make sure to:

- add your interceptor last, so that it wraps the network call and doesn't get cache misses or any other errors that may be emitted by upstream interceptors.
- call `retryOnError(false)` when forwarding the request downstream so that the retry is not made twice.

```kotlin
internal class MyRetryInterceptor(private val networkMonitor: NetworkMonitor?): ApolloInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
// Disable Apollo's built-in retry mechanism
val newRequest = request.newBuilder().retryOnError(false).build()
return chain.proceed(newRequest)
.onEach {
if (it.exception != null && it.exception.shouldRetry()) {
throw RetryException
}
}.retryWhen { cause, _->
if (cause is RetryException) {
// Add your logic here
true
} else {
// Programming error, re-throw it
false
}
}
}
}
```

2 changes: 2 additions & 0 deletions docs/source/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@
},
"Advanced": {
"Uploading files": "/advanced/upload",
"Monitoring your network (experimental)": "/advanced/network-connectivity",
"Handling nullability (experimental)": "/advanced/nullability",
"Experimental WebSocketNetworkTransport (experimental)": "/advanced/experimental-websockets",
"Using aliases": "/advanced/using-aliases",
"Using with Java": "/advanced/java",
"Kotlin native": "/advanced/kotlin-native",
Expand Down
1 change: 0 additions & 1 deletion docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ This library is designed primarily with Android in mind, but you can use it in a
* Auto Persisted Queries
* Query batching
* File uploads
* Espresso IdlingResource
* Fake models for tests
* AppSync and graphql-ws websockets
* GraphQL AST parser
Expand Down

0 comments on commit 3c5634a

Please sign in to comment.