Migrating to Apollo Kotlin 4.0
From 3.x
Note: as version 4 is currently under development, this page is a work in progress.
Dependencies / compatibility
- Apollo Kotlin 4 now requires the Kotlin Gradle Plugin version 1.6+ (whereas 3.x needed 1.5+).
- Compatibility with non-hierarchical multiplatform projects (
enableCompatibilityMetadataVariant
) has been removed.
Error handling
Exception surfacing
In v3, non-GraphQL errors like network errors, cache misses, and WebSocket protocol errors were surfaced by throwing exceptions in ApolloCall.execute()
and in Flows (ApolloCall.toFlow()
, ApolloCall.watch()
). This was not ideal because it was a difference in how to handle GraphQL errors vs other errors. Moreover, throwing terminates a Flow and consumers would have to handle re-collection.
In v4, a field ApolloResponse.exception
has been added and these errors are now surfaced by returning (for execute()
) or emitting (for Flows) an ApolloResponse
with a non null exception
instead of throwing it.
In addition to this, GraphQL errors are also surfaced in an ApolloGraphQLException
set on exception
(.errors
still exists for convenience).
This allows consumers to handle different kinds of errors at the same place, and it avoids Flows from being terminated.
To ease the migration to v4, the v3 behavior can be restored by calling ApolloClient.Builder.useV3ExceptionHandling(true)
.
// Replacetry {val response = client.query(MyQuery()).execute()if (response.hasErrors()) {// Handle GraphQL errors} else {// No errorsval data = response.data// ...}} catch (e: ApolloException) {// Handle network error}// Withval response = client.query(MyQuery()).execute()val data = response.dataif (data != null) {// No errors// ...} else {when (response.exception) {is ApolloGraphQLException -> {// Handle GraphQL errors}else -> {// Handle network error}}}// Replaceclient.subscription(MySubscription()).toFlow().collect { response ->if (response.hasErrors()) {// Handle GraphQL errors}}.catch { e ->// Handle network error}// Withclient.subscription(MySubscription()).toFlow().collect { response ->val data = response.dataif (data != null) {// No errors// ...} else {when (response.exception) {is ApolloGraphQLException -> {// Handle GraphQL errors}else -> {// Handle network error}}}}
Exception folding when using the cache
In v3, when using the normalized cache, you could set emitCacheMisses
to true
to emit cache misses instead of throwing.
In v4, this is now the default behavior and emitCacheMisses
has been removed.
With the CacheFirst
, NetworkFirst
and CacheAndNetwork
policies, cache misses and network errors are now emitted in ApolloResponse.exception
.
To ease the migration to v4, the v3 behavior can be restored by calling ApolloClient.Builder.useV3ExceptionHandling(true)
.
Exceptions in watch
In v3, ApolloCall.watch()
would accept a fetchThrows
and refetchThrows
parameters to control whether to throw cache or network exceptions happening during the initial fetch or subsequent refetches, the default value being false
.
In v4, these exceptions are now emitted in ApolloResponse.exception
.
If they are undesirable, ignore them by filtering them out:
client.query(MyQuery()).watch().filter { it.exception == null }.collect { response ->// Handle responses}
HTTP headers
In v3, if HTTP headers were set on an ApolloCall
, they would replace the ones set on ApolloClient
. In v4 they are added instead by default. To replace them, call ApolloCall.Builder.ignoreApolloClientHttpHeaders(true)
.
// Replaceval call = client.query(MyQuery()).httpHeaders(listOf("key", "value")).execute()// Withval call = client.query(MyQuery()).httpHeaders(listOf("key", "value")).ignoreApolloClientHttpHeaders(true).execute()
Multi-module improvements
In a multi-module setup, the syntax for depending on an upstream module (e.g. the schema module) has changed: use dependsOn
in the apollo
block instead of apolloMetadata
in the dependencies
block.
// feature1/build.gradle.kts// Replacedependencies {// ...// Get the generated schema types (and fragments) from the upstream schema moduleapolloMetadata(project(":schema"))// You also need to declare the schema module as a regular dependencyimplementation(project(":schema"))}// Withdependencies {// ...// You still need to declare the schema module as a regular dependencyimplementation(project(":schema"))}apollo {service("service") {// ...// Get the generated schema types (and fragments) from the upstream schema moduledependsOn(project(":schema"))}}
Auto-detection of used types
In multi-module projects, by default, all the types of an upstream module are generated because there is no way to know in advance what types are going to be used by downstream modules. For large projects this can lead to a lot of unused code and an increased build time.
To avoid this, in v3 you could manually specify which types to generate by using alwaysGenerateTypesMatching
. In v4 this can now be computed automatically by detecting which types are used by the downstream modules.
To enable this, add the "opposite" link of dependencies with isADependencyOf()
.
// schema/build.gradle.ktsapollo {service("service") {// ...// Enable generation of metadata for use by downstream modulesgenerateApolloMetadata.set(true)// Get used types from the downstream moduleisADependencyOf(project(":feature1"))// You can have several downstream modulesisADependencyOf(project(":feature2"))}}
Code generation
- Enum class names now have their first letter capitalized, as other generated types.
- To avoid a name clash with the introspection type of the same name, the
__Schema
type is now generated in aschema
subpackage (instead oftype
) when using thegenerateSchema
option.
Cache
The normalized cache must be configured before the auto persisted queries, configuring it after will now fail (see https://github.com/apollographql/apollo-kotlin/pull/4709).
apollo-ast
The AST classes (GQLNode
and subclasses) as well as Introspection
classes are not data classes anymore (see https://github.com/apollographql/apollo-kotlin/pull/4704/).
Gradle configuration
- Publishing is no longer configured automatically.
- Because Apollo Kotlin now supports different operation manifest formats,
operationOutput.json
has moved from"build/generated/operationOutput/apollo/$service/operationOutput.json"
to"build/generated/manifest/apollo/$service/operationOutput.json"