Set up a GraphQL client with Apollo

Apollo gives a neat abstraction layer and an interface to your GraphQL server. You don't need to worry about constructing your queries with request body, headers and options, that you might have done with OkHttp or Retrofit say. You can directly write queries and mutations in GraphQL and they will automatically be sent to your server via your apollo client instance.

Android Apollo Installation

Let's get started by adding apollo client & peer graphql dependenices to the project:

  • The latest gradle plugin version is Download . We will use the latest snapshot.
  • To use this plugin, add the dependency to your projects build.gradle file
buildscript {
repositories {
jcenter()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
dependencies {
classpath 'com.apollographql.apollo:apollo-gradle-plugin:1.0.1-SNAPSHOT'
}
}

Then add the following to your app's build.gradle dependencies:

repositories {
jcenter()
}
dependencies {
implementation 'com.apollographql.apollo:apollo-runtime:1.0.1-SNAPSHOT'
}
  • The plugin can then be applied as follows within your app module's build.gradle :
apply plugin: 'com.apollographql.android'

Note: The Android Plugin must be applied before the Apollo plugin

Adding code generation

  • The code generated by the plugin uses jetbrains annotations, so you will need to include this as a compile time dependency in your project's build.gradle:
dependencies {
compileOnly 'org.jetbrains:annotations:13.0'
testCompileOnly 'org.jetbrains:annotations:13.0'
}
  • Now create a graphqlinside the maindirectory and create new directory structure like graphql/com/hasura/todoso that the Apollo plugin can generate java classes with valid package.

Create .graphql files with your queries or mutations

Apollo generates code from queries and mutations contained in .graphql files in your target.

  • Create a file for GraphQL queries api.graphql with below query and place it inside the folder we just created
query AllTodos {
todos {
title
}
}
  • Next thing that we need to add a schema to the project. To download the schema, you need to get your access token from graphiql https://learn.hasura.io/graphql/graphiql.
  • Run this from your terminal to download the schema,
apollo schema:download --endpoint=http://learn.hasura.io/graphql --header="Authorization: Bearer <token>"
  • Place the downloaded schema.json in the same folder as your api.graphql

Detailed instructions to download your schema using the apollo CLI HERE

  • We also need to add javax annotation processor for the generated code,
implementation 'javax.annotation:jsr250-api:1.0'

Build your target

  • Compile your project to have Apollo generate the appropriate Java classes with nested classes for reading from the network response. In the sample project, a GetAllTodosQuery Java class is created here app/build/generated/source/apollo/classes/com.hasura.todo/

Note: This is an autogenerated file by Apollo and should not be changed manually

  • Before we start making requests, we need to add couple of more dependencies in app's build.gradle. OkHttp for networking interface
implementation "com.squareup.okhttp3:okhttp:3.13.1"
implementation "com.squareup.okhttp3:logging-interceptor:3.13.1"

Custom Scalar Types

Apollo supports Custom Scalar Types like Date. You first need to define the mapping in your build.gradle file. This will tell the code generator/gradle plugin what type to use when generating the classes.

  • Add this custom scalar for our timestampz field in our graphql API's. This also goes in app's build.gradle
apollo {
customTypeMapping = [
"timestampz" : "java.util.Date"
]
}

Create apollo client

You can use the generated classes to make requests to your GraphQL API. Apollo includes an ApolloClient that allows you to edit networking options like pick the base url for your GraphQL Endpoint.

In our project, we have the base url pointing to https://learn.hasura.io/graphql/

There is also a #query && #mutation instance method on ApolloClient that can take as input any Query or Mutation that you have generated using Apollo. Create a Network.kt under network folder on the root of the project. com.hasura.todo.Todo

package com.hasura.todo.Todo.network
import android.content.Context
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.ResponseField
import com.apollographql.apollo.cache.normalized.CacheKey
import com.apollographql.apollo.cache.normalized.CacheKeyResolver
import com.apollographql.apollo.cache.normalized.lru.EvictionPolicy
import com.apollographql.apollo.cache.normalized.lru.LruNormalizedCacheFactory
import com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper
import com.apollographql.apollo.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo.response.CustomTypeAdapter
import com.apollographql.apollo.response.CustomTypeValue
import com.apollographql.apollo.subscription.WebSocketSubscriptionTransport
import com.hasura.todo.type.CustomType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.text.ParseException
import java.text.SimpleDateFormat
private val GRAPHQL_ENDPOINT: String = "https://learn.hasura.io/graphql"
private val SQL_CACHE_NAME = "hasuratodo"
class Network {
companion object{
@JvmStatic
lateinit var apolloClient: ApolloClient
}
fun setApolloClient(accessTokenId: String, context: Context){
val log: HttpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val authHeader = "Bearer $accessTokenId"
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(log)
.addInterceptor { chain ->
val original = chain.request()
val builder = original.newBuilder().method(original.method(), original.body())
builder.header("Authorization", authHeader)
chain.proceed(builder.build())
}
.build()
val dateCustomTypeAdapter = object : CustomTypeAdapter<String> {
var ISO8601 = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ")
override fun decode(value: CustomTypeValue<*>): String {
try {
return ISO8601.parse(value.value.toString()).toString()
} catch (e: ParseException) {
throw RuntimeException(e)
}
}
override fun encode(value: String): CustomTypeValue<*> {
return CustomTypeValue.GraphQLString(value)
}
}
val apolloSqlHelper = ApolloSqlHelper(context, SQL_CACHE_NAME)
val normalizedCacheFactory = LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION)
.chain(SqlNormalizedCacheFactory(apolloSqlHelper))
val cacheKeyResolver: CacheKeyResolver = object : CacheKeyResolver() {
override fun fromFieldRecordSet(field: ResponseField, recordSet: Map<String, Any>): CacheKey {
if (recordSet.containsKey("todos")) {
val id = recordSet["todos"] as String
return CacheKey.from(id)
}
return CacheKey.NO_KEY
}
override fun fromFieldArguments(field: ResponseField, variables: Operation.Variables): CacheKey {
return CacheKey.NO_KEY
}
}
apolloClient = ApolloClient.builder()
.serverUrl(GRAPHQL_ENDPOINT)
.okHttpClient(okHttpClient)
.normalizedCache(normalizedCacheFactory, cacheKeyResolver)
.addCustomTypeAdapter(CustomType.TIMESTAMPTZ, dateCustomTypeAdapter)
.build()
}
}

We have to pass the tokenID to be set in apollo client so that we can make authorised calls to our graphql backend. Change below in login.kt

override fun onSuccess(credentials: Credentials) {
+ // Set Apollo Client
+ val network = Network()
+ network.setApolloClient(credentials.idToken!!, application)
credentialsManager?.saveCredentials(credentials)
showNextActivity()
}

We are all set to make graphql calls from our android app