Error handling
You can use response.data
to check if the server returned valid data:
val response = apolloClient.query(ExampleQuery()).execute()// Using 'data' to be able to do a smart cast below (KT-8819)val data = response.datawhen {data != null -> {// data can be smart cast to non-null hereprintln("The server returned data: $data")}else -> {// an error happened, check response.exception for more details or just display a generic errorwhen (response.exception) {is ApolloGraphQLException -> // do something with exception.errorsis ApolloHttpException -> // do something with exception.statusCodeis ApolloNetworkException -> TODO()is ApolloParseException -> TODO()else -> // generic error}}}
This is also true when calling toFlow()
(e.g. with subscriptions) and watch()
(with the normalized cache).
apolloClient.subscription(subscription).toFlow().collect { response ->if (response.data != null) {// handle data} else {// handle exceptions}}
If you prefer throwing, you can use dataOrThrow()
to get a non-null data:
val data = apolloClient.query(ExampleQuery()).execute().dataOrThrow()// data is non-null
Different types of errors
Whenever you execute a GraphQL operation with Apollo Kotlin (or any other GraphQL client), two high-level types of errors can occur:
- Network errors: a GraphQL response wasn't received because an error occurred while communicating with your GraphQL server. This might be an SSL error, a socket error because your app is offline, or a 500 or any other HTTP error. When a network error occurs, no data is returned.
- GraphQL errors: a GraphQL response is received, and it contains a non-empty
errors
field. This means the server wasn't able to completely process the query. The response might include partial data if the server was able to process some of the query.
GraphQL errors
For example, the following query uses an invalid id
to look up a Person
:
query FilmAndPersonQuery {film(id: "ZmlsbXM6MQ==") {title}person(id: "badId") {name}}
The server will send the following response:
{"data": {"film": {"title": "A New Hope"},"person": null},"errors": [{"message": "No entry in local cache for https://swapi.dev/api/people/m�H/","locations": [{"line": 35,"column": 3}],"path": ["person"]}]}
Note that while there are errors, the query successfully returned the title of the film: A New Hope
. In general, any error while executing an operation bubbles up to the next nullable field. In this case, person
is nullable. In the worst case, response.data
can be null if everything else is non-nullable.
When GraphQL errors happen, ApolloResponse.exception
will be an instance of ApolloGraphQLException
and you can use exception.errors
to read the actual GraphQL errors.
Network errors
Network errors are more varied and can have different causes (non-exhaustive list):
- The app is offline or doesn't have access to the network.
- A DNS error occurred, making it impossible to look up the host.
- An SSL error occurred (e.g., the server certificate isn't trusted).
- The connection was closed.
- The server responded with a non-successful HTTP code.
- The server didn't respond with valid JSON.
- The response JSON doesn't satisfy the schema and cannot be parsed.
- A request was specified as CacheOnly but the data wasn't cached.
- More...
Ignoring partial data
Because errors may happen on any field in your query, your UI code needs to handle any nullable field. In the example above, data.person will be null
:
query FilmAndPersonQuery {# wrong id here, null will be returnedperson(id: "badId") {name}}
Your UI code needs to check for null:
if (data.person != null) {// display person}
If you prefer to catch these errors at parsing time, you can make data.person
non-nullable using the @nonnull
directive:
extend type Query @nonnull(fields: "person")
Alternatively, you can check for errors in an interceptor and force data to null whenever a GraphQL error happens:
object ignorePartialDataInterceptor: ApolloInterceptor {override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {return chain.proceed(request).map {if (it.exception is ApolloGraphQLException) {// nullify data altogetherit.newBuilder().data(null).build()} else {it}}}}
Note that the null check is still required on person
because this field has a nullable type in the GraphQL schema.