Migrating to Apollo Kotlin 5
Step-by-step guide on migrating from Apollo Kotlin 4
Apollo Kotlin 5 is an incremental evolution of Apollo Kotlin 4 and keeps the same package name.
In most cases, bumping the version should be transparent. The exceptions are:
Symbols that were
DeprecationLevel.ERRORin v4 are now removed. Remove all your deprecated usages before migrating to v5.apollo-compileris still considered experimental. You will need to update your Apollo Compiler Plugins.
Read below for more details.
apollo-gradle-plugin-external
The Apollo Gradle Plugin now uses classloader isolation and does not use R8 to relocate dependencies anymore. As a result, the apollo-gradle-plugin-external artifact and the com.apollographql.apollo.external plugins are not used anymore and now point to an empty plugin. You should use apollo-gradle-plugin and com.apollographql.apollo instead:
1// Replace
2implementation("com.apollographql.apollo:apollo-gradle-plugin-external:4.3.3")
3// With
4implementation("com.apollographql.apollo:apollo-gradle-plugin:4.3.3")
5
6plugins {
7 // Replace
8 id("com.apollographql.apollo.external")
9 // With
10 id("com.apollographql.apollo")
11}apollo-gradle-plugin
operationOutputGenerator and operationIdGenerator are removed.
While running your OperationOutputGenerator directly in your build script classpath was convenient, it required the compiler code to run completely in the global buildscript classpath. This created many issues such as incompatible dependencies and/or unneeded build invalidations.
The v5 Apollo Gradle plugin runs the Apollo compiler in isolated classloaders, meaning generating the ids needs to happen in that same classloader using the ServiceLoader API.
To do so, use ApolloCompilerPlugin:
1class MyPlugin : ApolloCompilerPlugin {
2 override fun beforeCompilationStep(
3 environment: ApolloCompilerPluginEnvironment,
4 registry: ApolloCompilerRegistry,
5 ) {
6 registry.registerOperationIdsGenerator {
7 it.map { OperationId(it.source.md5(), it.name) }
8 }
9 }
10}Read more in the persisted queries and compiler plugins pages.
downloadApolloSchema is removed
Apollo Kotlin 4 allowed downloading a schema with a downloadApolloSchema Gradle task. This task is now removed.
For single-shot downloads of a schema during development, use the Apollo Kotlin CLI. It is much faster (no Gradle configuration), comes with interactive help and soon autocomplete scripts.
Alternatively, configure your introspection block. Doing so documents how to update the schema for other developers and makes it easy to update the schema without passing extra arguments.
1apollo {
2 service("service") {
3 packageName.set("com.example")
4
5 // This creates a downloadServiceApolloSchemaFromIntrospection task
6 introspection {
7 endpointUrl.set("https://example.com/graphql/endpoint")
8 // The path is interpreted relative to the current project
9 schemaFile.set(file("src/main/graphql/schema.graphqls"))
10 }
11 }
12}
13
14// You can create a shorthand lifecycle task name
15tasks.register("downloadSchema") {
16 dependsOn("downloadServiceApolloSchemaFromIntrospection")
17}apollo-idling-resource
Apollo Kotlin 5 removes the apollo-idling-resource artifact. Usage of ApolloIdlingResource has been decreasing, and better alternatives for testing are now available.
For a good overview of alternative solutions, we recommend reading this article from Jose Alcérreca.
apollo-compiler
@nonnull is an error
Apollo Kotlin 4 had a @nonnull client directive to force generating fields as non-null.
Since @nonnull, we've worked hard with the nullability working group to improve the handling of null types in GraphQL.
As part of this effort, it was recognized that the nullability information belongs to the schema. Fields that are only nullable for error reasons can now be marked with @semanticNonNull:
1type User {
2 email: String @semanticNonNull
3}
4
5# or if you don't own the schema, use extensions
6extend type User @semanticNonNullField(name: "email")The client can then decide how to handle errors with @catch:
1query GetUser {
2 user {
3 # generated as `String?` (current default)
4 email @catch(to: NULL)
5 # generated as `Result<String, Error>`
6 email @catch(to: RESULT)
7 # generated as `String`, throws if there is an error
8 email @catch(to: THROW)
9 }
10}You can read more in the "handling nullability" page.
apollo-runtime
New WebSockets
In Apollo Kotlin 5, the WebSocket code has been rewritten to simplify it and clarify the error and retry semantics.
All the classes in the com.apollographql.apollo.network.ws package have been deprecated and a new implementation is available in com.apollographql.apollo.network.websocket.
To migrate, replace all your instances of com.apollographql.apollo.network.ws with com.apollographql.apollo.network.websocket:
1// Replace
2import com.apollographql.apollo.network.ws.AppSyncWsProtocol
3import com.apollographql.apollo.network.ws.WebSocketNetworkTransport
4
5// With
6import com.apollographql.apollo.network.websocket.AppSyncWsProtocol
7import com.apollographql.apollo.network.websocket.WebSocketNetworkTransportSome shorthand methods on ApolloClient.Builder have been deprecated and replaced by explicit configuration. In those cases, you can use subscriptionNetworkTransport directly.
For an example, you can replace ApolloClient.Builder.webSocketEngine() as follows:
1// Replace
2ApolloClient.Builder()
3 .webSocketEngine(webSocketEngine)
4 .build()
5// With
6ApolloClient.Builder()
7 .subscriptionNetworkTransport(
8 WebSocketNetworkTransport.Builder()
9 .webSocketEngine(webSocketEngine)
10 .build()
11 )
12 .build()The default WebSocket protocol has changed to graphql-ws.
If you were already using it before, you may remove the call to protocol() or define it explicitly using subscriptionNetworkTransport():
1// Replace
2val apolloClient = ApolloClient.Builder()
3 .protocol(GraphQLWsProtocol.Factory())
4 .build()
5
6// With
7val apolloClient = ApolloClient.Builder()
8 .subscriptionNetworkTransport(
9 WebSocketNetworkTransport.Builder()
10 .serverUrl(url)
11 .wsProtocol(GraphQLWsProtocol())
12 .build()
13 )
14 .build()
15If you are still relying on the (now deprecated) transport, you can use SubscriptionWsProtocol:
1val apolloClient = ApolloClient.Builder()
2 .subscriptionNetworkTransport(
3 WebSocketNetworkTransport.Builder()
4 .serverUrl(url)
5 .wsProtocol(SubscriptionsWsProtocol())
6 .build()
7 )
8 .build()The retry management is now moved to retryOnErrorInterceptor:
1// Replace
2val apolloClient = ApolloClient.Builder()
3 .webSocketServerUrl("http://localhost:8080/subscriptions")
4 .webSocketReopenWhen { e, attempt ->
5 delay(2.0.pow(attempt.toDouble()).toLong())
6 // retry after the delay
7 true
8 }
9
10// With
11val apolloClient = ApolloClient.Builder()
12 .webSocketServerUrl("http://localhost:8080/subscriptions")
13 .retryOnErrorInterceptor(RetryOnErrorInterceptor { context ->
14 if (context.request.operation is Subscription<*>) {
15 delay(2.0.pow(context.attempt.toDouble()).toLong())
16 true
17 } else {
18 false
19 }
20 })apollo-http-cache
apollo-http-cache is now deprecated. Instead, it uses the existing OkHttp cache using cacheUrlOverride.
You can remove the apollo-http-cache artifact:
1dependencies {
2 // Remove
3 implementation("com.apollographql.apollo:apollo-http-cache:4.3.3")
4}Configure your ApolloClient with a cache-enabled OkHttpClient and set enablePostCaching to true:
1// Replace
2val apolloClient = ApolloClient.Builder()
3 .serverUrl(mockServer.url())
4 .httpCache(directory = "http_cache", maxSize = 10_000_000)
5 .build()
6
7// With
8val apolloClient = ApolloClient.Builder()
9 .networkTransport(
10 HttpNetworkTransport.Builder()
11 // Enable POST caching
12 .httpRequestComposer(DefaultHttpRequestComposer(serverUrl = mockServer.url(), enablePostCaching = true))
13 .httpEngine(
14 DefaultHttpEngine {
15 OkHttpClient.Builder()
16 // Make sure to use a different directory for your cache as the format changed
17 .cache(directory = File(application.cacheDir, "http_cache2"), maxSize = 10_000_000)
18 .build()
19 }
20 )
21 .build()
22 )
23 .build()The OkHttp cache uses the standard Cache-Control HTTP header. Compared to Apollo Kotlin 4 and HttpFetchPolicy the semantics are different but you can map most of the concepts:
CacheFirst:
1// Replace
2apolloClient.query(query).httpFetchPolicy(HttpFetchPolicy.CacheFirst)
3
4// With
5apolloClient.query(query) // no need to add a cache-control header, this is the defaultCacheOnly:
1// Replace
2apolloClient.query(query).httpFetchPolicy(HttpFetchPolicy.CacheOnly)
3
4// With
5apolloClient.query(query).addHttpHeader("cache-control", "only-if-cached")NetworkOnly:
1// Replace
2apolloClient.query(query).httpFetchPolicy(HttpFetchPolicy.CacheOnly)
3
4// With
5apolloClient.query(query).addHttpHeader("cache-control", "no-cache")We believe this new caching scheme is simpler and more aligned with the rest of the ecosystem, but there are important differences with the previous scheme:
There is no equivalent to
NetworkFirstand/orhttpExpireTimeout().The cache keys have changed, meaning your cache will be invalidated.
If either of these limitations is important for your use case, please open an issue.