10. Authenticate your queries


In this section, you will book a flight 🚀! Booking a flight requires being authenticated to the server so the correct person is sent to space! To do that, and since Apollo Android is using OkHttp to handle HTTP requests, you will use OkHttp Interceptors to add headers to your GraphQL requests.

Add the interceptor

In Apollo.kt, add the AuthorizationInterceptor class:

Kotlin
app/src/main/kotlin/com/example/rocketreserver/Apollo.kt
1private class AuthorizationInterceptor(val context: Context): Interceptor {
2    override fun intercept(chain: Interceptor.Chain): Response {
3        val request = chain.request().newBuilder()
4            .addHeader("Authorization", User.getToken(context) ?: "")
5            .build()
6
7        return chain.proceed(request)
8    }
9}

This interceptor appends an "Authorization: $token" HTTP header to every request.

Use the interceptor

Create a custom OkHttpClient that will use this interceptor and pass it to the ApolloClient:

Kotlin
app/src/main/kotlin/com/example/rocketreserver/Apollo.kt
1    instance = ApolloClient.builder()
2        .serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/")
3        .okHttpClient(OkHttpClient.Builder()
4            .addInterceptor(AuthorizationInterceptor(context))
5            .build()
6        )
7        .build()

Notice that Apollo.kt now requires a context to create the interceptor and read the token from the EncryptedSharedPreferences. apolloClient cannot be a top level variable anymore, so transform it to a top-level method that takes a context parameter instead:

Kotlin
app/src/main/kotlin/com/example/rocketreserver/Apollo.kt
1fun apolloClient(context: Context): ApolloClient {
2    return ApolloClient.builder()
3        .serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/")
4        .okHttpClient(OkHttpClient.Builder()
5            .addInterceptor(AuthorizationInterceptor(context))
6            .build()
7        )
8        .build()
9}

Replace all usages of apolloClient with apolloClient(context). There is one instance in each fragment:

Kotlin
1// LaunchListFragment
2apolloClient(requireContext()).query(LaunchListQuery(cursor = Input.fromNullable(cursor))).await()
3
4// LaunchDetailsFragment
5apolloClient(requireContext()).query(LaunchDetailsQuery(id = args.launchId)).await()
6
7// LoginFragment
8apolloClient(requireContext()).mutate(LoginMutation(email = Input.fromNullable(email))).await()
9

There is no need to create a new instance of the apolloClient for each GraphQL request. You can reuse a single instance like so:

Text
1private var instance: ApolloClient? = null
2
3fun apolloClient(context: Context): ApolloClient {
4    if (instance != null) {
5        return instance!!
6    }
7
8    instance = ApolloClient.builder()
9        .serverUrl("https://apollo-fullstack-tutorial.herokuapp.com/")
10        .okHttpClient(
11            OkHttpClient.Builder()
12            .addInterceptor(AuthorizationInterceptor(context))
13            .build()
14        )
15        .build()
16
17    return instance!!
18}

Note: The singleton is handled here manually for sample purposes. In a real-life application, you would certainly use a dependency injection framework such as Dagger or Koin.

Add the BookTrip and CancelTrip mutations

Next to schema.json add a BookTrip.graphql file:

GraphQL
app/src/main/graphql/com/example/rocketreserver/BookTrip.graphql
1mutation BookTrip($id:ID!) {
2  bookTrips(launchIds: [$id]) {
3    success
4    message
5    launches {
6      id
7    }
8  }
9}

Notice how the bookTrips field takes a list as argument but the mutation itself only take a single variable.

Also add the CancelTrip.graphql file. This mutation doesn't use lists:

GraphQL
app/src/main/graphql/com/example/rocketreserver/CancelTrip.graphql
1mutation CancelTrip($id:ID!) {
2  cancelTrip(launchId: $id) {
3    success
4    message
5    launches {
6      id
7    }
8  }
9}

Connect the mutations to your UI

Connect the mutations to the Book Now button:

Go back to LaunchDetailsFragment.kt. In the button click listener, after checking for the user token, add the following code:

Kotlin
app/src/main/java/com/example/rocketreserver/LaunchDetailsFragment.kt
1binding.bookButton.visibility = View.INVISIBLE
2binding.bookProgressBar.visibility = View.VISIBLE
3
4lifecycleScope.launchWhenResumed {
5    val mutation = if (isBooked) {
6        CancelTripMutation(id = args.launchId)
7    } else {
8        BookTripMutation(id = args.launchId)
9    }
10
11    val response = try {
12        apolloClient(requireContext()).mutate(mutation).await()
13    } catch (e: ApolloException) {
14        configureButton(isBooked)
15        return@launchWhenResumed
16    }
17
18    if (response.hasErrors()) {
19        configureButton(isBooked)
20        return@launchWhenResumed
21    }
22
23    configureButton(!isBooked)
24}

This sends the appropriate mutation based on whether the launch is booked.

Book your trip!

Compile and run your app. You can now book and cancel your trips!

In the next section, you will write your first subscription and be notified in real time when someone books a flight.

Feedback

Edit on GitHub

Forums