AndroidのGraphQLクライアントをapolloで実装

# はじめに
趣味でAndroidアプリを開発しているのですが、サーバーサイドとのやり取りをGraphQLを用いて行ってみようとしたときに、インターネット上の情報がやや古く、公式のページを見ても幾つか引っかかった箇所があったので、自分の備忘録的に記述します。

# サーバーサイド側
普段はRailsのエンジニアをしているので、サーバーサイドはRailsを用いました。サーバー側の実装は興味ない方は読み飛ばしてください。

rubyでGraphQLを使うには、[graphql-ruby](http://graphql-ruby.org/)というgemがあり、Gemfileに追加するだけで使える様になります。

```rb:Gemfile
gem 'graphql'
```

まずは、rails generateコマンドでgraphqlを使うのに必要な諸々のファイルを生成します。

```
rails g graphql:install
```
app/graphql ディレクトリが生成されていたり、config/routes.rb にgraphql関連のエンドポイントが追加されます。そして、それを処理する graphql_controller.rb も app/controllers配下に生成されます。

また、GraphQLの開発においては、[GraphiQL](https://github.com/graphql/graphiql)（Qの前にiがあるので注意！）という実行環境アプリを用いると便利で、javascriptで開発されているのですが、それをRailsEngineとしてラップした`graphiql-rails`というgemもGemfileに追加されているかと思います。config/routes.rbに以下の様なルーティングが追加されていることからも`/graphiql`でアクセスできそうです。

```ruby:config/routes.rb
if Rails.env.development?
  mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end
```
開発中はこれを用いれば良いのですが、本番環境や他のGraphQLのエンドポイントを叩きたいときに便利なのが、GraphiQLのアプリです。Electronでデスクトップアプリ化されているのが提供されているので、Macの方は

```
brew cask install graphiql
open /Applications/GraphiQL.app
```
でインストールできます。何かとあると便利なのでインストールしておくと良いと思います。


```
rails g graphql:object Movie id:ID! url:String! key:String! title:String! description:String! published_at:String 
rails g graphql:object Category id:ID! name:String! movies:[Movie]
```
generateしたあとは、ファイルを直接修正することもできます。
app/graphql/types/movie_type.rb
app/graphql/types/category_type.rb
などがオブジェクトを定義しているファイルです。

```ruby:app/graphql/types/category_type.rb
module Types
  class CategoryType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    field :movies, [Types::MovieType], null: true do
      argument :num, Integer, required: false
    end

    def movies(num: 8)
      object.movies.limit(num)
    end
  end
end
```
```ruby:app/graphql/types/movie_type.rb
module Types
  class MovieType < Types::BaseObject
    field :id, ID, null: false
    field :url, String, null: false
    field :key, String, null: false
    field :status, String, null: false
    field :title, String, null: false
    field :description, String, null: false
    field :published_at, String, null: false
  end
end
```
```ruby:app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :category, CategoryType, null: true do
      argument :id, ID, required: true
    end

    def category(id:)
      Category.find(id)
    end
  end
end
```
だいたいこんな感じで定義してあげればとりあえず動くものができます。
開発環境であれば、http://localhost:3000/graphiql にアクセスして簡単なクエリを発行すればレスポンスが返ってきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/121304/9b17c265-b6ca-496c-36ba-3133ecbaec91.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/121304/7750d4a4-769e-5c22-e8cc-4b204013d304.png)

簡単にサーバーサイド側の準備を行いました。今回はRailsで作りましたが、お好きなもので実装すれば良いかと思います。


# Android側
Androidアプリ側でAPIの呼び出しをするとき、これまでのRESTfulなAPIであれば、Retrofit2などのライブラリを用いることが多いでしょう。GraphQLは情報を取得するときもPOSTですが、当初はRetrofit2でPOSTを実装すれば良いと思っていました。しかし、GraphQLでは、apolloというのを用いるのが多い様です。今回もapolloを用いてGraphQLのClient側の実装を行いました。
導入についてはApolloのチームがandroid向けのドキュメントを整備してくれています[（こちら）](https://www.apollographql.com/docs/android/)。ただ、ちょっと情報がわかりにくく、少し苦労しましたので、自分の備忘録も込めて記述したいと思います。
基本的にはこちらの[Get started](https://www.apollographql.com/docs/android/essentials/get-started.html)に沿って説明します。

## 必要なpluginをbuild.gradleに追加
まず必要なpluginをbuild.gradleに追加します。プロジェクトディレクトリ直下にあるbuild.gradleのdependenciesに以下のclasspathを追加します。
mavenを見ると[apollo-gradle-plugin](https://mvnrepository.com/artifact/com.apollographql.apollo/apollo-gradle-plugin)と[gradle-plugin](https://mvnrepository.com/artifact/com.apollographql.apollo/gradle-plugin)の二つがありますが、apollo-gradle-pluginの方を使用します。

```diff:build.gradle
  buildscript {
      dependencies {
          // 省略
+         classpath 'com.apollographql.apollo:apollo-gradle-plugin:0.5.0'
      }
  }
```
app配下にあるbuild.gradleの方に次の２行を追加します。`apply plugin: 'com.apollographql.android'`に関してはAndroid Pluginの下に書く必要があるので、`apply plugin`の一番下に追加すれば良いでしょう。

```diff:app/build.gradle
  apply plugin: 'com.android.application'
　　　　apply plugin: 'kotlin-android'
　　　　apply plugin: 'kotlin-android-extensions'
+ apply plugin: 'com.apollographql.android'

  // 省略

  dependencies {
    // 省略
+   implementation 'com.apollographql.apollo:apollo-runtime:0.5.0'
  } 
```
バージョンはいずれも`0.5.0`にしました。過去の記事をみると`0.4.1`などを利用しているものもあるのですが、うまく動かない場合があります。基本、最新のものを利用すれば良いでしょう。
build.gradleへの追加が終わったら、Syncして反映してあげてください。

## schema.jsonを置く
サーバーのGraphQLエンドポイントのスキーマを定義したファイルをAndroidプロジェクト内に置く必要があります。これを元に、apolloは、開発者が記述したgraphqlのクエリが妥当であるかなどのチェックをし、javaファイルに変換します。

schema.jsonを取得するには、`apollo-codegen`コマンドを利用します。[^1]
`apollo-codegen`コマンドが未インストールの方は

```bash
yarn global add apollo-codegen
```
でインストールしてください。`yarn`が入っていない方は`brew install yarn`でインストールできます。`apollo-codegen`が正しくインストールされているかバージョンを確認してみます。

```bash
$ apollo-codegen --version
0.20.2
```
次にschema.jsonをダウンロードします。

```bash
apollo-codegen download-schema https://hogehoge.com/graphql --output schema.json
```
schema.jsonは私の場合、
app/src/main/graphql/com/hogehoge/schema.json
に置きましたが、.graphqlと同じディレクトリに置く必要があります。[^2]

## .graphqlファイルの作成
今回は、以下の様な形で実装しました。

```graphql:Category.graphql
query Category($id: ID!, $num: Int) {
  category(id: $id) {
    id
    name
    movies(num: $num) {
      id
      title
      publishedAt
    }
  }
}
```
このファイルを元にpluginがjavaクラスを生成してくれます。このときのクラス名は`CategoryQuery`となります。
１行目に`query Category(・・・) {`と書いてありますが、そこの名称+Queryという形になります。
かっこの中は引数です。今回の場合、CategoryのID（`$id`）と紐づく動画のとってくる数（`$num`）を変数にしています。
このクエリが間違っているとコンパイル時にエラーになるので、GraphiQLなどで正しいかどうかを確認しておくと良いかもしれません。GraphiQLでは、左下のQUERY VARIABLESで変数をセットできます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/121304/e3f5d51f-604a-6026-6451-7415942ddfe6.png)

ここで余談ですが、`movies`のところは`ResponseField.forList`というメソッドで実装されるのですが、[v0.4.1](https://github.com/apollographql/apollo-android/blob/v0.4.1/apollo-api/src/main/java/com/apollographql/apollo/api/ResponseField.java)では未実装で、この様な形はもしかしたら当時はうまくいかなかったかもしれません。[v0.4.2](https://github.com/apollographql/apollo-android/blob/v0.4.2/apollo-api/src/main/java/com/apollographql/apollo/api/ResponseField.java)からはある様です。

## 利用する側での実装
okHttpClientとapolloClientを用意するところは、（[参考](https://www.apollographql.com/docs/android/essentials/get-started.html#creating-client)）の通りです。
`apolloClient.query`の引数は先ほど作成した.graphqlファイルになります。まだビルドしていない場合は、`CategoryQuery`クラスが生成されていないのでエラーになるかもしれませんので、.graphqlファイルを追加したタイミングでビルドしてみると良いかもしれません。
.graphqlファイルで定義した引数のところは、メソッドになるようです。

```kotlin
CategoryQuery.builder()
             .id(2) // カテゴリーIDの奇数。$id
             .num(6) // 動画の数の引数。$num
             .build()
```

```kotlin
    val okHttpClient = OkHttpClient.Builder().build()
    val apolloClient = ApolloClient.builder()
        .serverUrl("https://hogehoge.com/graphql") // サーバのホストのは、ローカルでサーバーを立てている場合、10.0.2.2になるらしいので注意。rails sで起動している場合は、http://10.0.2.2:3000となる。10.0.2.2でうまくいかない場合は、10.0.3.2なども試してみる。
        .okHttpClient(okHttpClient)
        .build()

    // APIで取得したCategoryを格納するListを用意
    // Categoryは簡単なdata classを別で定義。 => data class Category(val name: String)
    val categories = mutableListOf<Category>()
    // APIで取得後、adapterに更新を通知するため。APIコールは別スレッド。
    val handler = Handler()

    apolloClient.query(
        CategoryQuery.builder().id("2").num(6).build()
    ).enqueue(
        // ApolloCall.Callbackを実装した無名クラスを作成。必要なメソッドをoverrideする。
        object : ApolloCall.Callback<CategoryQuery.Data>() {
          override fun onResponse(response: Response<CategoryQuery.Data>) {
            response.data()?.category()?.forEach {
              categories.add(Category(it.name())) // サンプルのため、moviesは無視。

              handler.post {
                // adapterはRecyclerView用のadapter
                adapter.categories = categories
                adapter.notifyDataSetChanged()
              }
            }
          }

          override fun onFailure(e: ApolloException) {
            Log.e("ApolloCall Failure", e.message, e)
          }
        }
    )
```
APIのところは、RxAndroidなどを用いればもう少しスッキリかけるかと思います。


[^1]: [apollo-cli](https://github.com/apollographql/apollo-cli)でも`schema:download`ができる様なのですが、Android Studioでコンパイルしたときに、`GraphQL schema file should contain a valid GraphQL introspection query result`のエラーがでてうまくいきませんでした。
[^2]: schema.jsonのpathを明示することも可能な様です。（[参考](https://github.com/apollographql/apollo-android#usage-5)）

# 参考
 - [Apollo GraphQL Client for Android](https://github.com/apollographql/apollo-android)
 - [APOLLO ANDROID GUIDE](https://www.apollographql.com/docs/android/)
 - [GitHub GraphQL API v4 を Android で遊んでみる](https://qiita.com/ssoejima/items/0ae0cb97aabfcac11c6a)
 - [Apollo-androidのレスポンスキャッシュを使ってみる](https://qiita.com/moto/items/a22fb9ad1a34aeeff305#_reference-80e3458c13b0be65d9e6)