GraphQL WebSocket subscriptions on Android using Apollo

3 min read

Recently my team and I worked on implementing WebSockets (known as subscriptions in GraphQL), to allow users to talk to each other in real-time. During this time, we saw that the documentation to this using the Apollo client for Android was pretty bad! So in this post, I will be talking about our findings and how this can be done, with the hopes that it might be helpful for others as well who might be having the same problem.

This post will not be about how to get started with Androids Graphql library Apollo, or what GraphQl is.

GraphQL subscriptions are a way to push data from the server to the clients that choose to listen to real time messages from the server. Subscriptions are similar to queries in that they specify a set of fields to be delivered to the client, but instead of immediately returning a single answer, a result is sent every time a particular event happens on the server.

A common use case for subscriptions is notifying the client side about particular events, for example the creation of a new object, updated fields and so on.

https://www.apollographql.com/docs/react/data/subscriptions/

Note: Everything that you will see here will be written for simplicity and understanding. There will be no Dagger or any special architecture or library usage, to make it easy for anyone to introduce into their codebase.

Let’s get started!

Defining a subscription

GraphQL subscriptions are pretty simple to define, just like we do with our queries and mutations, we define subscriptions in our .graphql files using the subscription keyword.

subscription onNewMessagesReceived($groupUUID: String!) {
    groupChatSubscription(group: $groupUUID) {
        ... on Message {
                uuid
        }
    }
}

Enabling Apollo WebSocket subscriptions

Once we have created our subscription, let’s enable them on our apollo client.

object HeadersProvider {

    val HEADERS = mapOf(
        "Authorization" to "Bearer XYZ",
        "other_shared_headers" to "etc.."
    )
}

class ApolloSubscriptionClientFactory {

    // ...

    fun createSubscriptionApolloClient(sharedOkHttpClientBuilder: OkHttpClient.Builder): ApolloClient {

        val okHttpClient = sharedOkHttpClientBuilder
            .pingInterval(KEEP_ALIVE_INTERVAL, TimeUnit.SECONDS)
            .build()

        val subscriptionTransportFactory =
        WebSocketSubscriptionTransport.Factory("wss://your_subscription_host/graphql", okHttpClient)

        return ApolloClient.builder()
            .serverUrl("https://your_host_url/graphql")
            .okHttpClient(okHttpClient)
            .subscriptionConnectionParams(HeadersProvider.HEADERS)
            .subscriptionTransportFactory(subscriptionTransportFactory)
            .build()
    }

    // ...
}

Note: Currently, there are no interceptors or straight forward ways for setting shared subscription connection params in the Android Apollo GraphQL library. This may result in outdated headers such as, for example, the authorization token. To avoid this, a different ApolloClient is created for every GraphQL subscription. This behavior will be obsolete when the following PR is merged/released: https://github.com/apollographql/apollo-android/pull/1688. Once this is done we will be able to simply update subscriptionConnectionParams, instead of creating a new client every time.

Code Explained

  • HeadersProvider contains shared headers to be used across multiple apollo clients.
  • sharedOkHttpClientBuilder The same OkHttp builder used for creating the original Apollo client, even though the Request headers are not shared, other configurations are shared.
  • okHttpClient the new OkHttp client created for subscriptions, with an optional interval.
  • subscriptionTransportFactory The most important piece here, this is what we use to enable subscriptions.

pingInterval(KEEP_ALIVE_INTERVAL, TimeUnit.SECONDS) is optional here, it is sometimes required to ping the endpoint every so often, to make sure the connection stays open.

WebSocketSubscriptionTransport.Factory takes the subscription URL which usually starts with wss:// and the OkHttpClient to use.

That is it, this is all it takes to enable subscriptions.

Subscribing at last!

This is the moment we have all been waiting for, actually subscribing and receiving data!

First, we create a simple generic function type that supports subscribing to any kind of GraphQL subscription (feel free to use your own).

class GraphQLApi(private val sharedOkHttpClientBuilder: OkHttpClient.Builder) {

    // ...

    fun <D : Operation.Data, V : Operation.Variables> subscribe(subscription: Subscription<D, D, V>): Flowable<Response<D>> {

        // Create our dedicated apollo client
        val apolloClient = ApolloSubscriptionClientFactory(sharedOkHttpClientBuilder)
            .createSubscriptionApolloClient()

        // Perform the actual subscription
        val call = apolloClient.subscribe(subscription)

        // Subscribe using Rx2Apollo
        return Rx2Apollo.from<D>(call)
    }

    // ...
}

All this function does is allow us to create a subscription of any type, the internals should be very similar to what you might be doing for queries and mutations, except this time we call apolloClient.subscribe instead.

And in order to subscribe to a subscription we simply do the following:

class SomePresenterOrViewModelEtc(private val api: GraphQLApi) {
    
    // ...

    fun sunscribeToMessages() {
        val messagesSubscription = OnNewMessagesReceivedSubscription.builder()
            .groupUUID("uuid")
            .build()

        api.subscribe(messagesSubscription)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                // handle results, like adding the result into a list
            }, {
                // handle errors
            }).addTo(compositeDisposable)
    }

    // ...
}

Conclusion

That is all! Enabling and subscribing to WebSocket subscriptions in GraphQL is actually pretty simple once you see it once, and they are a lot of fun to work with. I hope you enjoyed the article and have as much fun as we are having with them here at Dubsmash, until next time!