Apollo GraphQL for Android


What is Apollo GraphQL?

Apollo is a platform for building a unified graph, a communication layer that helps us manage the flow of data between our application clients (such as web and native apps) and our back-end services. At the heart of the graph is a query language called GraphQL.

Our graph sits between application clients and back-end services, facilitating the flow of data between them

Now let’s see how we can use Apollo Kotlin(formerly known as Apollo Android) in our applications to communicate with a GraphQl server.

Step 1: Configure our project

  1. Apply the apollo plugin in app’s build.gradle
plugins {
    id("com.apollographql.apollo3").version("3.8.2")
}

2. Add Apollo GraphQl dependency

dependencies {
    // ...
    implementation("com.apollographql.apollo3:apollo-runtime:3.8.2")
}

Note: At the time of writing this blog, latest version was 3.8.2. You can check the current version from this link.

Step 2: Add the GraphQl schema

Let’s use the same schema as an example as we used in the previous article

schema {
    query: Query
}
type Product {
    id: ID!
    name: String!
    description: String
    imageUrl: String
    active: Boolean!
    productVariants(product_Id: Float): [ProductVariant]
    brand: String
}
type ProductVariant {
    id: ID!
    product: ProductNode!
    attribute: String
    active: Boolean!
    inventory: Float
}
type Query {
    allProducts(id: Int, name: String, brand: String): [Product]
    allVariantsOfProduct(productId: Int): Product
}

Note: I am just using this schema as an example to explain the concepts. You will have to use the schema given by your backend team.

  1. Create a folder named graphql at app/src/main and then create folders as per your package name.
  2. Create a file named schema.graphqls at this location and copy the schema text you will get from backend
  3. Build our project after adding the schema so that you can write your queries.
app/src/main/graphql/com/example/tanya/schema.graphqls

Note: Make sure the schema has extension .graphqls and not .graphql

Step 3: Write our Query

Let’s see how a query needs to be written in android using allProducts query from the schema as an example

  1. Create a query file (eg. named allProducts.graphql) at the same location we created the schema.
  2. Add the query in our file
query AllProducts {
  allProducts {
    cursor
    hasMore
    products {
       id,
       name, 
       description
       imageUrl
    }
  }
}

3. Build our project. Once we build the project, the system will generate a model class corresponding to our query (in our example AllProductsQuery) at the location

app/build/generated/source/apollo/service/com/example/tanya/AllProductsQuery.kt

which will look something like this

public data class AllProductsQuery(): Query<AllProductsQuery.Data> {
  
  ..... 
  public data class Data(
      public val allProducts: AllProducts?
  ) : Query.Data
  ....  
  public data class AllProducts(
      public val products: List<Product?>
  )
  ....
  public data class Product(
      public val id: String,
      public val description: Int,
      public val name: String,
      public val imageUrl: String?
 )
 ....
}

To get the list of products we will have to access allProductsQuery. data?.allProducts?.products

4. Now in our component classes, we can access the data using this model class

Step 4: Execute our Query

  1. Create a Apollo Client
import com.apollographql.apollo3.ApolloClient

val apolloClient = ApolloClient.Builder().httpMethod(HttpMethod.Get)
    .serverUrl("https://your-server-base-url.com/graphql")
    .build()

2. Execute our query using the .query method provided by the apolloClient

Query for our example will look like this:

lifecycleScope.launchWhenResumed {
    val response: AllProductsQuery.Data = try {
         apolloClient.query(AllProductsQuery()).execute()
    } catch (e: ApolloException) {
         Log.d("ProductList", "Failure", e)
         null
    }
}

Step 5: Update the UI from the response

  1. Get the list of data (products for us) from the response
val products= response?.data?.allProducts?.products?.filterNotNull() 

2. Send the list to your adapter which will accept data of type AllProductsQuery.Product for us

class ProductListListAdapter(
    private val products: List<AllProductsQuery.Product>
) : RecyclerView.Adapter<ProductListAdapter.ViewHolder>() {
....
   override fun onBindViewHolder(holder: ViewHolder, position: Int){
        val product = products.get(position)
        holder.binding.name.text = product.name ?: ""
   }
....
}

Step 6: Filtering of Data

Let’s see how we can get a single product by id in our example

If we were using REST APIs, we would have asked the back-end team to create a new API for us. But, in our case we wouldn’t be needing that. We can use the same schema to write a query to get a single product without any added support from backend.

allProducts(id: Int, name: String): [Product]

query AllProducts($id: String, $name: String) {
  allProducts {
    cursor
    hasMore
    products {
       id,
       name, 
       description
       imageUrl
    }
  }
}

In Apollo Android, all the parameters on which we want to filter the data have to be prefixed with a dollar ($) sign.

Execute query for filtered products

1. Get product by id

apolloClient.getInstance().query(
    AllProductsQuery(
        id = Optional.Present(id)
    )
).execute()

2. Get product by name

apolloClient.getInstance().query(
    AllProductsQuery(
        name = Optional.Present(name)
    )
).execute()

Step 7: Error Handling in Apollo GraphQL APIs

try {
   val response = apolloClient.query(AllProductsQuery()).execute()
} 
catch (e: ApolloException) {
   when (exception) {
      is ApolloNetworkException -> {
         if (exception.cause is SocketTimeoutException) {
            showToast("Network timeout")
          }
         else if(exception.cause is UnknownHostException){
            showToast("Check your internet connection")
          }
         else{
             showToast("Network error")
          }
         }
     is ApolloHttpException -> {
        showToast(exception.message.toString())
     }
     else -> {
        showToast("Unknown network error")
     }
   }
}

GraphQL errors

In this case, GraphQL response is received, and it contains a non-empty errors field. This means the server wasn’t able to completely process the query. The response might include partial data if the server was able to process some of the query.

The server response can look something like this:

{
  "data": {
    "product": {
      "name": "ABC"
    }
  },
  "errors": [
    {
      "message": "Error message",
      "locations": [
        {
          "line": 35,
          "column": 3
        }
      ],
      "path": [
        "variant"
      ]
    }
  ]
}

How to handle GraphQL errors?

Each object in the errors list contains a message, locations, path, extensions, nonStandardFields. We can use any on these fields to handle the user experience on the UI.

try {
   val response = apolloClient.query(AllProductsQuery()).execute()
   if(response.errors != null){
       response.errors?.getOrNull(0)?.message?.let{
            showToast(it)
       } ?: let{
            showToast("Network error is null)
       }
   }
   else if(response.data != null){
       bindData()
   }
   else{
       showToast("Network data is null)
}
catch (e: ApolloException) {
//Handle exceptions
....
}

That’s it for this article. Hope it was helpful!

Link to the first part of this series:

Introduction to GraphQL


Leave a comment