How to configure Apollo to work with GraphQL in Android

Why an article


Recently, I had a need to implement working with a backend in GraphQL. There are not so many tutorials for setting up on Android, unlike REST, and most of them are no longer quite relevant.

What is GraphQL


GraphQL is a fashionable alternative to the REST API, which allows you to query data in a more optimized way, giving only the data you need.

Environment setting


We will be making requests to the server through Apollo - the most popular library for working with GraphQL at the moment.

Let's get to work. First of all, let's add the necessary permissions for working with the network to the manifest:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Next, you need to connect apollo. We go to the main build.gradle and in the dependencies section add the following line:

classpath 'com.apollographql.apollo:apollo-gradle-plugin:2.0.0'

Now you need to add additional dependencies in the build.gradle file of the app module:

implementation("com.apollographql.apollo:apollo-runtime:2.0.0")
implementation "com.apollographql.apollo:apollo-android-support:2.0.0"

In addition, add the plug-in connection to the very top of the file:

apply plugin: 'com.apollographql.apollo'

After the project is synchronized, we need to configure the code generation of the models with which we will make queries to GraphQL.

Let's minimize the development environment and open a terminal. Go to the folder with your project:

cd /Users/user/Desktop/ProjectName

If you don’t have npm yet, first download from the official site

Install apollo-codegen - a tool that allows you to download schema.json - a file that will serve as apollo source for generating models:

npm install apollo-codegen

Download schema.json (you must be in the directory of your project, where the node_modules folder appeared):

node_modules/.bin/apollo-codegen download-schema https://   api/ --output schema.json

Now in the project folder we see the schema.json file. It remains to show apollo files for generating models. To do this, take the following steps.

Go to the app folder of your project, then src -> main. Here we need to create a graphQL folder. Here we will add our .graphql files.

We copy the file downloaded in the previous step to the created folder - schema.json
The environment is set up, go to the code

Model Code Generation


Let's start with the .graphql files. Requests will be stored here.
GraphQL has two types of queries:
query - an analogue of GET
mutation - an analogue of POST / PUT / DELETE

Suppose that you are making a service for checking in guests into a room in a hotel for administrators of this hotel. The application can do 3 functions. Login user (administrator), get room information by id and populate guests

Create a loginUser.graphql file in the app / src / main / graphQL directory created in the previous section. Using this file apollo will generate a model for username.

File content:

mutation loginUser($email:String!, $password:String!) {
    login(
        user: {
            email: $email,
            password: $password
        }
    ){
        email,
        token,
        refreshToken
    }
}

In addition, we need the getRoom.graphql file, with its help a model is generated to get the hotel room:

query getRoom($room_id: String) {
    room(room_id: $room_id) {
        title,
        room_number,
        cheked_in_family_name,
        has_minibar
    }
}

And the final file is checkUserIn.graphql check-in. Also uses mutation:

mutation checkInFamily($room_id: String!, $family_name: String!) {
    room(
        room: {
            title: $family_name,
            room_id: $room_id
        }
    ){
        room_id,
        family_name,
        minibar_products{
            title,
            weight
        }
    }
}

We build the project and see 3 generated models in the app / build / generated / source / apollo / debug / service folder: GetRoomQuery, CheckUserInMutation, LoginUserMutation

Query execution


Create a singleton NetworkService class that will provide us with ApolloClient. We make 2 methods in it. getApolloClient () to execute requests that do not require a token or any additional parameters, and getApolloClientWithTokenInterceptor (), into which we will throw the token, for requests:

import com.apollographql.apollo.ApolloClient
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request

class NetworkService {

  fun getApolloClient(): ApolloClient {
    val okHttp = OkHttpClient
      .Builder()
      .build()

    return ApolloClient.builder()
      .serverUrl(BASE_URL)
      .okHttpClient(okHttp)
      .build()
  }

  fun getApolloClientWithTokenInterceptor(token: String): ApolloClient {

    val httpClient = OkHttpClient.Builder()
      .addInterceptor(Interceptor { chain: Interceptor.Chain ->
        val original: Request = chain.request()
        
        val builder: Request.Builder = original
          .newBuilder()
          .method(original.method, original.body)
        
        builder.header("Authorization", "Bearer $token")
        return@Interceptor chain.proceed(builder.build())
      })
      .build()

    return ApolloClient.builder()
      .serverUrl(BASE_URL)
      .okHttpClient(httpClient)
      .build()
  }

  companion object {
    private var mInstance: NetworkService? = null

    fun getInstance(): NetworkService? {
      if (mInstance == null) {
        mInstance = NetworkService()
      }
      return mInstance
    }
  }
}

Now we go to our activity or fragment, here we implement the execution of requests. To get started, log in:

  private fun loginUser() {

    val client = NetworkService.getInstance()?.getApolloClient()
    val loginMutation = LoginUserMutation
      .builder()
      .email(emailEdit.text.toString())
      .password(passwordEdit.text.toString())
      .build()

    client
      ?.mutate(loginMutation)
      ?.enqueue(object : ApolloCall.Callback<LoginUserMutation.Data>() {

        override fun onResponse(response: Response<LoginUserMutation.Data>) {
          if (!response.hasErrors()) {
            val token = response.data?.login()?.token()
            val email = response.data?.login()?.email()
            // ,   ui,     
            runOnUiThread {
              //   ,  
            }
          }
        }

        override fun onFailure(e: ApolloException) {}
      })
  }

This is an example of working with mutation. We performed this request without a token in the header. Now let's look at an example of working with query, try to get information about the room. Suppose that a room is given to us only with a token. We execute the request as follows:

  private fun getRoom() {
    val token = "123456"
    val client = NetworkService.getInstance()
      ?.getApolloClientWithTokenIntercetor(token)

    val roomId = "123"
    val allRoomsQuery = GetRoomQuery(Input.fromNullable(roomId))

    client
      ?.query(allRoomsQuery)
      ?.enqueue(object : ApolloCall.Callback<GetRoomQuery.Data>() {

        override fun onResponse(response: Response<GetRoomQuery.Data>) {
          if (!response.hasErrors()) {
            val familyName = response.data?.room()?.family_name()
          }
        }

        override fun onFailure(e: ApolloException) {}
      })
  }

As a homework, try to write yourself an implementation of settling guests.

Useful links:

apollo-client documentation for Android
Github apollo-client android

All Articles