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. addTypename = "ifFragments"
is deprecated as it could lead to cache misses for cache users:
{node(id: 42) {# no fragment here means but we if we don't query __typename, the query below will emit a cache misstitleprice}}{product(id: 42) {# __typename is added here automatically__typename... on Product {titleprice}}}
Instead, use "always"
if you're using the cache or "ifPolymorphic"
else. See #3965 for more details.
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/). The class hierarchy has been tweaked so that GQLNamed
, GQLDescribed
and GQLHasDirectives
are more consistently inherited from.
GQLSelectionSet
and GQLArguments
are deprecated and removed from GQLField
and GQLInlineFragment
. Use .selections
directly
GQLInlineFragment.typeCondition
is now nullable to account for inline fragments who inherit their type condition.
SourceLocation.position
is renamed SourceLocation.column
and is now 1-indexed. GQLNode.sourceLocation
is now nullable to account for the cases where the nodes are constructed programmatically.
It is not possible to create a Schema
from a File or String directly anymore. Instead, create a GQLDocument
first and convert it to a schema with toSchema()
.
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"
Removed RxJava artifacts
- Use
toFlow().asFlowable()
andkotlinx-coroutines-rx2
orkotlinx-coroutines-rx3