Handle WebSocket errors


Because the WebSocket is a shared resource, error handling is different from regular queries. In HTTP queries, an error has a single consumer. With WebSockets, an error may have multiple consumers.

WebSocket errors with multiple consumers

When your network goes down or your server closes the connection (because it wants a new token or something else), all the subscriptions are terminated. You could retry them individually. The example below uses a very naive exponential backoff algorithm:

Kotlin
1apolloClient.subscription(MySubscription())
2    .toFlow()
3    .retryWhen { e, attempt ->
4      delay(2.0.pow(attempt.toDouble()).toLong())
5      // retry after the delay
6      true
7    }
8    .collect {
9      println("trips booked: ${it.data?.tripsBooked}")
10    }

This has a number of shortcomings (not mentioning the naive exponential backoff implementation):

  • This code has to be implemented in different places

  • The attempt is tied to the collector lifecycle, there is no way to reset it if the network is back up

Instead you can manage this in a more central place when configuring your ApolloClient:

Kotlin
1    val apolloClient = ApolloClient.Builder()
2        .httpServerUrl("http://localhost:8080/graphql")
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        }

The above code will remember all current subscriptions and resubscribe to the server automatically without having to call retry in each screen.

WebSocket errors with single consumers

In some cases, you may have a situation where an error is for a single consumer. This happens typically for error messages:

JSON
1{
2  "id": "3f32558f-49a3-4dc2-9d1c-445adf0a66c7",
3  "type": "error",
4  "payload": { }
5}

In these cases, the Flow will throw a SubscriptionOperationException error and you will need to handle the retry:

Kotlin
1apolloClient.subscription(MySubscription())
2    .toFlow()
3    .retryWhen { e, attempt ->
4      if (e is SubscriptionOperationException) {
5        // handle the operation error
6      } else {
7        // handle global errors that were not caught by webSocketReopenWhen
8      }
9    }
10    .collect {
11      println("trips booked: ${it.data?.tripsBooked}")
12    }
Feedback

Edit on GitHub

Forums