11. Write your first subscription


In this section, you will use subscriptions to get notified whenever someone books a flight 🚀! Subscriptions allow to be notified in real time whenever an event happens on your server. The fullstack backend supports subscriptions based on WebSockets.

Write your subscription

Open your Sandbox back up, click on the Schema tab at the far left. In addition to queries and mutations, you will see a third type of root fields, subscriptions. Click on subscriptions to see the tripsBooked field:

The definition of tripsBooked in the schema

This field doesn't take any argument and returns a single scalar named tripsBooked. Since you can book multiple trips at once, tripsBooked is an Int. It will contain the number of trips booked at once or -1 if a trip has been cancelled.

Click the play button to the left of tripsBooked to open the field in Explorer. Open a new tab, then check the plus button next to tripsBooked to have the field added:

The initial definition of the TripsBooked subscription

Again, rename your subscription so it's easier to find:

The subscription after rename

Click the Submit Operation button, and your subscription will start listening to events. You can tell it's up and running because a panel will pop up at the lower left where subscription data will come in:

The UI showing that it's listening for subscription updates

Test your subscription

Open a new tab in Explorer. In this new tab, add code to book a trip like on step 9, but with a hard-coded ID:

GraphQL
1mutation BookTrip {
2  bookTrips(launchIds: ["93"]){
3    message
4  }
5}

Do not forget to include the authentication header. At the bottom of the Sandbox Explorer pane where you add operations, there's a Headers section:

Adding a login token to explorer

Click the Submit Operation button. If everything went well, you just booked a trip! At the top of the right panel, you'll see the success JSON for your your BookTrip mutation, and below it, updated JSON for the TripsBooked subscription:

Subscription success in Explorer

Continue booking and/or canceling trips, you will see events coming in the subscription panel in real time. After some time, the server might close the connection and you'll have to restart your subscription to keep receiving events.

Add the subscription to the project

Now that your subscription is working, add it to your project. Create a file named TripsBooked.graphql next to schema.graphqls and your other GraphQL files and paste the contents of the subscription. The process is similar to what you did for queries and mutations:

GraphQL
app/src/main/graphql/TripsBooked.graphql
1subscription TripsBooked {
2  tripsBooked
3}

Configure your ApolloClient to use subscriptions

The tutorial uses the subscriptions-transport-ws protocol for subscriptions. For more options like the newer graphql-ws or Appsync, see GraphQL over WebSocket protocols.

In Apollo.kt, configure a webSocketServerUrl for your ApolloClient:

Kotlin
Apollo.kt
1val apolloClient = ApolloClient.Builder()
2    .serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/graphql")
3    .webSocketServerUrl("wss://apollo-fullstack-tutorial.herokuapp.com/graphql")
4    .okHttpClient(...)
5    .build()

wss:// is the protocol for WebSocket.

Display a SnackBar when a trip is booked/cancelled

In MainActivity, register your subscription and keep a reference to the returned coroutine Flow.

Use collectAsState to get the latest value of the Flow as a state. When the Flow emits a value, it will be stored in tripBookedResponse and trigger a recomposition of the UI thanks to the LaunchedEffect that depends on it.

Kotlin
MainActivity.kt
1RocketReserverTheme {
2    val tripBookedFlow = remember { apolloClient.subscription(TripsBookedSubscription()).toFlow() }
3    val tripBookedResponse: ApolloResponse<TripsBookedSubscription.Data>? by tripBookedFlow.collectAsState(initial = null)
4    LaunchedEffect(tripBookedResponse) {
5        if (tripBookedResponse == null) return@LaunchedEffect
6        val message = when (tripBookedResponse!!.data?.tripsBooked) {
7            null -> "Subscription error"
8            -1 -> "Trip cancelled"
9            else -> "Trip booked! 🚀"
10        }
11        // TODO use the message
12    }

Now let's display the message in a Material SnackBar.

To do this, you'll need to create a SnackbarHostState and call showSnackbar on it. Don't forget to also pass it to the Scaffoldbelow:

Kotlin
MainActivity.kt
1val snackbarHostState = remember { SnackbarHostState() }
2val tripBookedFlow = (...)
3(...)
4    val message = (...)
5    snackbarHostState.showSnackbar(
6        message = message,
7        duration = SnackbarDuration.Short
8    )
9}
10
11Scaffold(
12    topBar = { TopAppBar({ Text(stringResource(R.string.app_name)) }) },
13    snackbarHost = { SnackbarHost(snackbarHostState) },
14) { paddingValues ->
15

Handle errors

Like for queries and mutations, the subscription will throw an error if the connection is lost or any other protocol error happens. To handle these situations, you can configure the client to retry the subscription with the webSocketReopenWhen function. Return true to retry, false to stop. To avoid retrying too often, you can use the attempt parameter to delay the retry:

Kotlin
Apollo.kt
1val apolloClient = ApolloClient.Builder()
2    (...)
3    .webSocketReopenWhen { throwable, attempt ->
4        Log.d("Apollo", "WebSocket got disconnected, reopening after a delay", throwable)
5        delay(attempt * 1000)
6        true
7    }

Test your code

Build and run your app and go back to Sandbox Explorer, and select the tab where you set up the BookTrip mutation. Book a new trip while your app is open, you should see a SnackBar 🚀:

A trip has been booked

This concludes the tutorial, congratulations! 🎉

More resources

Use the rest of this documentation for more advanced topics like Caching or Gradle configuration.

Feel free to ask questions by either opening an issue on our GitHub repo, joining the community or stopping by our channel in the KotlinLang Slack(get your invite here).

And if you want to dig more and see GraphQL in real-world apps, you can take a look at these open source projects using Apollo Kotlin:

Feedback

Edit on GitHub

Forums