Xcoding with Alfian

Software Development Videos & Tutorials

Using Github GraphQL Search API in Android App with Apollo

Alt text

GraphQL had gained popularity in recent years, it makes querying data from API becomes much more expressive, customisable, and flexible because of its strongly typed system, function to resolve that can be defined on each fields and types, and supports for nested types. It also makes querying data become much more efficient between client and server because the server can resolve the function by querying the data from multiple data source into one single request.

In this article, we will build an Android app that lists all the current trending repositories in GitHub by calling Github GraphQL API endpoint using Apollo Android GraphQL library.

What we will build:

  1. Integrating Apollo Android dependencies into our project.
  2. Import schema from Github GraphQL into our project.
  3. GraphQL query file for searching repository that will be used by Apollo Android to build generated Java class.
  4. Data Repository class as an interface to get the latest repository from Github using the generated Java class and Apollo Client.
  5. ViewModel class that retrieves the data from the Data Repository class that is observed by the Activity/Fragment then display the result to UI.

Apollo Android Integration

To use Apollo Android in our project we need to add dependencies to our app and module build gradle file.

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.apollographql.apollo:apollo-gradle-plugin:0.5.0'
  }
}

dependencies {
  compile 'com.apollographql.apollo:apollo-runtime:0.5.0'
}

Import GitHub GraphQL API Schema

We also need to import the GitHub GraphQL API schema into our app. To access the GitHub API we need to generate access token for our app from GitHub homepage, then we can use apollo-codegen npm package to download the schema passing our access token.

apollo-codegen introspect-schema https://api.github.com/graphql --output schema.json --header "Authorization: Bearer <token>

After the schema json file has been downloaded, we need to import it to our app project directory.

Alt text

Creating GitHub GraphQL query file

We need to create a file with a .graphql extension inside the same directory as our schema.json file, in this case we named it github.graphql then we will put our GitHub GraphQL repositories query inside this file. Apollo Android will generate Java class for each query specified inside this file.

query GetLatestTrendingRepositoriesInLastWeek($first: Int, $query: String!, $type: SearchType!) {
  search(first: $first, query: $query, type: $type) {
    edges {
      node {
        ... on Repository {
          name
          owner {
            login
          }
          description
          stargazers {
            totalCount
          }
          primaryLanguage {
            name
          }
        }
      }
    }
  }
}

Try to rebuild the project and then take a peek inside the build generated directory, there will be a Java class named GetLatestTrendingRepositoriesInLastWeekQuery that is generated based on the schema and query file in our project.

Alt text

Data Repository class as a layer to get data from GraphQL API

import GetLatestTrendingRepositoriesInLastWeekQuery
import com.apollographql.apollo.ApolloCall
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.exception.ApolloException
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import type.SearchType
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.concurrent.TimeUnit

class DataRepository {

    fun getLatestTrendingRepositoriesInLastWeek(completion: (result: Pair<List<GetLatestTrendingRepositoriesInLastWeekQuery.Edge>?, Error?>) -> Unit) {
        val lastWeekDate = LocalDate.now().minusDays(7)
        val formattedDateText = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH).format(lastWeekDate)
        val queryCall = GetLatestTrendingRepositoriesInLastWeekQuery
                .builder()
                .query("created:>$formattedDateText sort:stars-desc")
                .first(25)
                .type(SearchType.REPOSITORY)
                .build()

        apolloClient.query(queryCall).enqueue(object: ApolloCall.Callback<GetLatestTrendingRepositoriesInLastWeekQuery.Data>() {
            override fun onFailure(e: ApolloException) {
                completion(Pair(null, Error(e.message)))
            }

            override fun onResponse(response: com.apollographql.apollo.api.Response<GetLatestTrendingRepositoriesInLastWeekQuery.Data>) {
                val errors = response.errors()
                if (!errors.isEmpty()) {
                    val message = errors[0]?.message() ?: ""
                    completion(Pair(null, Error(message)))
                } else {
                    completion(Pair(response.data()?.search()?.edges() ?: listOf(), null))
                }
            }
        })
    }

    companion object {

        private val GITHUB_GRAPHQL_ENDPOINT = "https://api.github.com/graphql"

        private val httpClient: OkHttpClient by lazy {
            OkHttpClient.Builder()
                    .writeTimeout(15, TimeUnit.SECONDS)
                    .readTimeout(15, TimeUnit.SECONDS)
                    .addNetworkInterceptor(NetworkInterceptor())
                    .build()
        }


        private val apolloClient: ApolloClient by lazy {
            ApolloClient.builder()
                    .serverUrl(GITHUB_GRAPHQL_ENDPOINT)
                    .okHttpClient(httpClient)
                    .build()
        }

        private class NetworkInterceptor: Interceptor {

            override fun intercept(chain: Interceptor.Chain?): Response {
                return chain!!.proceed(chain.request().newBuilder().header("Authorization", "Bearer <TOKEN>").build())
            }
        }

    }

}

We create DataRepository class that will be used as a layer to get access to the GitHub GraphQL API using Apollo Client. We use okhttp library as the HTTP client and also add network interceptor that will set the HTTP header with the GitHub Access Token that we generate from GitHub.

we expose public method getLatestTrendingRepositories that returns list of repositories from Github GraphQL API. Inside this method we build the query using the generated class builder. We get last week date and format it to match the GitHub date query format, then we pass the query containing the created date is later than last week and for the results to be sorted my most stars in descending order. We also set the builder to get only 25 first result and the type of the search to be Repository type.

We won’t build our HTTP request by ourselves because Apollo Android has generate the java class for us to query repository with strongly typed parameter and return type without caring about the underlying HTTP request.

With the generated java class we can create a Builder for the query containing the parameters that can be passed to the Query such as limit for pagination. The parameter type are also strongly typed based on the schema.json Github API we have imported to our project directory.

Repository View Model

Our RepositoryViewModel class exposes reposResult LiveData that will be observed by the Activity/Fragment that will display the result in UI. The loadRepos call the DataRepository getLatestTrendingRepositories method that returns the repositories or error and update the LiveData that will trigger update with the results to all of the observers and then use the data to update UI using RecyclerView or ListView.

import GetLatestTrendingRepositoriesInLastWeekQuery
import com.alfianlosari.graphql_github.api.DataRepository

class TrendingRepositoriesViewModel: ViewModel() {

    private val dataRepository = DataRepository()

    var reposResult = MutableLiveData<Pair<List<GetLatestTrendingRepositoriesInLastWeekQuery.Edge>?, Error?>>()

    init {
        loadRepos()
    }

    fun loadRepos() {
        dataRepository.getLatestTrendingRepositoriesInLastWeek {
            val handler = Handler(Looper.getMainLooper())
            handler.post {
                reposResult.value = it
            }
        }
    }

}

Note that the Github GraphQL API returns Edges and Nodes for the result, inside our query file we have specified the return to be casted as Repository type so Apollo can generate the class for us. To access the repository type we can easily cast the node into the AsRepository class then access the repository data from there.

class RepositoryViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {

    fun bindRepository(repo: GetLatestTrendingRepositoriesInLastWeekQuery.Node) {
        if (repo is GetLatestTrendingRepositoriesInLastWeekQuery.AsRepository) {
            itemView.name.text = repo.name()
            itemView.owner.text = repo.owner().login()
            itemView.description.text = repo.description()
            itemView.star_count.text = "${repo.stargazers().totalCount()} ✭"
            itemView.language.text = repo.primaryLanguage()?.name()
        }
    }
}

Conclusion

Using GraphQL API in an Android app become more easier and simpler with Apollo GraphQL. Apollo also supports customised response caching mechanism , custom scalar type adapter mapping. GitHub GraphQL API make querying data to GitHub much more flexible and customisable. I think GraphQL really improve the process of communication between client and server that can produce rapid development time for developers. You can access the source code for the app in the Github Repository.