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 . 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
graphql
inside themain
directory and create new directory structure likegraphql/com/hasura/todo
so 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://hasura.io/learn/graphql/graphiql
. - Run this from your terminal to download the schema,
apollo schema:download --endpoint=https://hasura.io/learn/graphql/graphiql --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 hereapp/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://hasura.io/learn/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.networkimport android.content.Contextimport com.apollographql.apollo.ApolloClientimport com.apollographql.apollo.api.Operationimport com.apollographql.apollo.api.ResponseFieldimport com.apollographql.apollo.cache.normalized.CacheKeyimport com.apollographql.apollo.cache.normalized.CacheKeyResolverimport com.apollographql.apollo.cache.normalized.lru.EvictionPolicyimport com.apollographql.apollo.cache.normalized.lru.LruNormalizedCacheFactoryimport com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelperimport com.apollographql.apollo.cache.normalized.sql.SqlNormalizedCacheFactoryimport com.apollographql.apollo.response.CustomTypeAdapterimport com.apollographql.apollo.response.CustomTypeValueimport com.apollographql.apollo.subscription.WebSocketSubscriptionTransportimport com.hasura.todo.type.CustomTypeimport okhttp3.OkHttpClientimport okhttp3.logging.HttpLoggingInterceptorimport java.text.ParseExceptionimport java.text.SimpleDateFormatprivate val GRAPHQL_ENDPOINT: String = "https://hasura.io/learn/graphql"private val SQL_CACHE_NAME = "hasuratodo"class Network {companion object{@JvmStaticlateinit 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 Stringreturn 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
- Build apps and APIs 10x faster
- Built-in authorization and caching
- 8x more performant than hand-rolled APIs