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:
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:
Again, rename your subscription so it's easier to find:
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:
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:
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:
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:
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:
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 newergraphql-ws
or Appsync, see GraphQL over WebSocket protocols.
In Apollo.kt
, configure a webSocketServerUrl
for your ApolloClient
:
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.
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 Scaffold
below:
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:
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 🚀:
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: