GitHub GraphQL API v4 を Android で遊んでみる

#Apollo GraphQL Client for Android

https://github.com/apollographql/apollo-android

>Apollo-Android is a GraphQL compliant client that generates Java models from standard GraphQL queries.

前置きなしにいきなり始めます。[GraphQL](http://graphql.org/) から Java コードを生成してくれます。このライブラリを利用して [GitHub API v4](https://developer.github.com/v4/) を利用する手順を紹介します。

##はじめる前に
Android プロジェクトを適当に作成して、必要なパーミッションとライブラリのインポートを行います。

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

プロジェクトの build.gradle の dependencies に以下を追加。

```groovy
dependencies {
    ...

    classpath 'com.apollographql.apollo:gradle-plugin:0.4.0'
}
```

モジュールの build.gradle に以下を追加（apollo プラグインは必ず andoird プラグインより下にします）。

```groovy
apply plugin: 'com.android.application'
apply plugin: 'com.apollographql.android'
```

オプショナルで RxJava2 を利用できるようにするライブラリも追加。

```groovy
dependencies {
    ...

    compile "io.reactivex.rxjava2:rxjava:2.1.1"
    compile "io.reactivex.rxjava2:rxandroid:2.0.1"
    compile "com.apollographql.apollo:apollo-rx2-support:0.4.0"
}
```

##Apollo 利用の基本手順

1. apollo-codegen をインストール
2. schema.json をダウンロード
3. GraphQL でクエリを作成
4. Java コードを生成
5. 生成したコードを使う
    - OkHttp で認証処理
    - ApolloClient を作成
    - メソッドの呼び出し

##1. apollo-codegen をインストール
GraphQL から Java を生成するツールをインストールします。このステップは初回のみ必要です。

```text
npm install -g apollo-codegen
```
必ず `-g` を付けて実行してください。グローバルにインストールしないと Android のプロジェクトからアクセスできないのでコードの生成ができません。

※npm がない人は [Node.js](https://nodejs.org/ja/) をインストールしてください。

##2. schema.json をダウンロード

Apollo でのコード生成には API の詳細が記述されている **schema.json** と実際のクエリを定義した **~.graphql** ファイルが必要です。まず schema.json を GitHub からダウンロードします。GitHub API のエンドポイントにアクセスするにはアクセストークンが必要なので以下の手順で先に取得しておきます。

GitHub で自分のプロフィール画像をクリックして設定を開きます。画面左下にある **Personal access tokens** を選択すると  **Generate new token** と云うボタンが出てくるので、そこでアクセストークンを生成できます。スコープは *user* と *repo* を使います。このデモではそれぞれユーザデータの取得、レポジトリの issue の更新に利用します。

*※個人利用限定のトークンです。扱いに注意してください。アプリで実際に使う場合は [OAuth](https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/) で簡単にアクセストークンを取得することができます。*

![github_settings.png](https://qiita-image-store.s3.amazonaws.com/0/188470/3928ccb8-9203-addc-937b-2570a31b9690.png)


アクセストークンの準備ができたら、apollo-codegen を使って schema.json をダウンロードします。

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

1MB 程ある schema.json がコマンドを実行したディレクトリにダウンロードされます（後で Java コードの生成に利用します）。

##3. GraphQL でクエリを作成
ユーザ情報を抜き出すだけの簡単なクエリを作ります。初めて GitHub の [GraphiQL](https://developer.github.com/v4/explorer/) を開いた時に出ているサンプルクエリに少し項目を追加したものです。Apollo はオペレーションとクエリ名を省略した形式の GraphQL をサポートしていないのでオペレーション名と適当なクエリ名を与える必要があります。処理はクエリなので `query` キーワードで、名前は `Viewer` と与えます。生成されるコードのクラス名は*クエリ名* + *オペレーション名*になるので *ViewerQuery* になります。

```text
query Viewer {
  viewer {
    login
    bio
    avatarUrl
    company
    location
  }
}
```
念のために GraphiQL でクエリを実行してみるとこんな感じ。

![fig2.png](https://qiita-image-store.s3.amazonaws.com/0/188470/16919dce-1934-62f4-3b8a-cb54f4680d2e.png)


このクエリを API.graphql と云う名前のファイルに普通のテキスト形式で保存します（ファイル名の *API* の部分は何でも構いません。拡張子は必ず .graphql にします）。
##4. コードの生成
上のステップで用意した schema.json と API.graphiql から Java コードを生成します。あらかじめ作って置いたプロジェクトの src/main に *graphql* と云うディレクトリを作成します。そこにアプリのパッケージ名に対応するディレクトリ構造を作成し、schema.json と API.graphql を配置します。生成されるクラス群はこのディレクトリ構造に従ったパッケージ名が与えられます。ここではアプリのルートパッケージに api と云うパッケージを追加した構造にします。Android Studio のパースペクティブを Project Files にして展開するとこんな感じになります。

![dirs.png](https://qiita-image-store.s3.amazonaws.com/0/188470/753cd131-a622-9a49-3a3b-fff4f33b1699.png)

この状態でアプリのビルドを行うと Apollo プラグインがコードを生成してくれます。生成されたコードは build/generated のディレクトリ以下にあります。

##5. 生成したコードを使う
生成したコードは ApolloClient を介して呼び出すことができます。schema.json をダウンロードした時と同様に、bearer token が要求されるので ApolloClient に認証処理を追加する必要があります。ApolloClient は OkHttp を HTTP クライアントとして利用できるので、OkHttp の Authenticator (もしくはInterceptor) を使って認証できるようにします。

```java
OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
            @Override
            public Request authenticate(Route route, okhttp3.Response response) throws IOException {
                return response.request().newBuilder()
                        .addHeader("Authorization", "Bearer <access-token>")
                        .build();
            }
        }).build();
```
ApolloClient を作成します。上で作った `okHttpClient` と API のエンドポイントを渡します。

```java
ApolloClient apolloClient = ApolloClient.builder()
        .okHttpClient(okHttpClient)
        .serverUrl("https://api.github.com/graphql")
        .build();
```
先のステップで生成したコードで GitHub にリクエストを送ります。

```java
Rx2Apollo.from(apolloClient.query(new ViewerQuery()).watcher())
        .subscribeOn(Schedulers.io())
        .subscribe(new DisposableObserver<Response<ViewerQuery.Data>>() {
            @Override
            public void onNext(Response<ViewerQuery.Data> dataResponse) {
                android.util.Log.d("graphql", "" + dataResponse.data());
            }

            @Override
            public void onError(Throwable e) {
                android.util.Log.d("graphql", "" + e);
            }

            @Override
            public void onComplete() {
                // no-op
            }
        });
```

成功するとこんな感じのレスポンスが返ってきます（生成されたコードにラップされる前の実際のレスポンスはJSONテキスト）。

```text
D/graphql: Data{viewer=Viewer{__typename=User, login=mightyfrog, name=, company=, avatarUrl=https://avatars0.githubusercontent.com/u/208494?v=4, bio=Android developer @DeNA, createdAt=2010-02-22T16:04:03Z}}
```

##引数を取るクエリを作ってみる
ユーザ名を指定してユーザデータを取得してみます。クエリはこんな感じ。

```text
{
  user(login: "mightyfrog") {
    id
    login
    bio
    avatarUrl
    company
    createdAt
  }
}
```
これを変数が使える形式に書き換えます。

```text
query User($login: String!) {
  user(login: $login) {
    id
    login
    bio
    avatarUrl
    company
    createdAt
  }
}
```
GraphiQL で試します。変数の値は QUERY VARIABLES に記述して送信します。

```text
{
  "login": "mightyfrog"
}
```

![fig3.png](https://qiita-image-store.s3.amazonaws.com/0/188470/0f4aac9b-c89c-a52a-803e-c4a548d1d664.png)


変数が使えるように書き換えたクエリを、先に作った API.graphql にこのように追加します。

```text:API.graphql
query Viewer {
  viewer {
    login
    name
    company
    avatarUrl
    bio
    createdAt
  }
}

query User($login: String!) {
  user(login: $login) {
    id
    login
    bio
    avatarUrl
    company
    createdAt
  }
}
```
プロジェクトをビルドすると新たに `UserQuery` クラスが生成されるので使ってみます。コンストラクタが引数を取るようになっています。

```java
Rx2Apollo.from(apolloClient.query(new UserQuery("mightyfrog")).watcher())
        .subscribeOn(Schedulers.io())
        .subscribe(new DisposableObserver<Response<UserQuery.Data>>() {
            @Override
            public void onNext(Response<UserQuery.Data> dataResponse) {
                android.util.Log.d("graphql", "" + dataResponse.data());
            }

            @Override
            public void onError(Throwable e) {
                android.util.Log.d("graphql", "" + e);
            }

            @Override
            public void onComplete() {
                // no-op
            }
        });
```
こんな感じの結果が返ってきます。

```text
D/graphql: Data{user=User1{__typename=User, id=MDQ6VXNlcjIwODQ5NA==, login=mightyfrog, bio=Android developer @DeNA, avatarUrl=https://avatars0.githubusercontent.com/u/208494?v=4, company=, createdAt=2010-02-22T16:04:03Z}}

```

##データを更新してみる
GitHub の [The Octocat](https://github.com/octocat) のアカウントの Hello-World レポジトリの [issue349](https://github.com/octocat/Hello-World/issues/349) に絵文字のリアクションを付けてみます。API.graphql に以下を追加してコード生成します。

```text
mutation AddReactionToIssue($subjectId: ID!, $content: ReactionContent!) {
  addReaction(input:{subjectId: $subjectId,content: $content}) {
    reaction {
      content
    }
    subject {
      id
    }
  }
}
```
GraphiQL で試すとこんな感じに。

![fig4.png](https://qiita-image-store.s3.amazonaws.com/0/188470/19f0c447-8281-42ac-cf03-20fe70ead89f.png)

下のクエリを使って issue 番号を取得すれば、自分のレポジトリでも試すことができます。

```text
query FindIssueID {
  repository(owner: "octocat", name: "Hello-World") {
    issue(number: 349) {
      id
    }
  }
}
```


ビルドして生成されたコードで実行します。

```java
Rx2Apollo.from(apolloClient.mutate(new AddReactionToIssueMutation("MDU6SXNzdWUyMzEzOTE1NTE=", ReactionContent.HEART)))
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new DisposableObserver<Response<AddReactionToIssueMutation.Data>>() {
            @Override
            public void onNext(Response<AddReactionToIssueMutation.Data> dataResponse) {
                mTextView.setText("" + dataResponse.data());
                android.util.Log.d("graphql", "" + dataResponse.data());
            }

            @Override
            public void onError(Throwable e) {
                android.util.Log.d("graphql", "" + e);
            }

            @Override
            public void onComplete() {
                // no-op
            }
        });
```
レスポンスはこんな感じ。

```text
D/graphql: Data{addReaction=AddReaction{__typename=AddReactionPayload, reaction=Reaction{__typename=Reaction, content=HEART}, subject=Subject{__typename=Issue, id=MDU6SXNzdWUyMzEzOTE1NTE=}}}
```

issue349 のリアクションも更新されます。

![reaction.png](https://qiita-image-store.s3.amazonaws.com/0/188470/44153dff-d1a8-af28-361a-ef53cec02f69.png)

##終わりに
Apollo GraphQL Client for Android はアルファとして一般公開されてからまだ３ヶ月ほどしか経っていないライブラリです。正直扱いがちょっと面倒なのと、パフォーマンス面も不明ですがこのデモ程度のことはちゃんとこなす事ができます。今回は使いませんでしたが alias、fragment、directive を使ったもっと複雑なクエリもサポートしています。[Kotlin プラグイン](https://github.com/apollographql/apollo-android#kotlin)も用意されていて、Proguard への配慮や、Auto-Value 対応予定であったりと今後も期待が持てるライブラリです。

ここまで読んでいただけた人はきっと GraphQL 自体にもより興味が持てたのではないかと思います。GraphQL をもっと知りたい！って人は GitHub の [GraphQL Overview](https://developer.github.com/v4/) と graphql.org の [Introduction to GraphQL](http://graphql.org/learn/) がオススメです。

shibuya.apk #17 で~~使った~~使う予定だったデモ GraphQL API Server はこちら：
https://github.com/mightyfrog/Simple-GraphQL-API-Server

##参考資料
- http://graphql.org/
- https://developer.github.com/v4/
- https://github.com/apollographql
- https://github.com/chentsulin/awesome-graphql
- https://code.facebook.com/posts/1691455094417024
- https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html
- https://githubengineering.com/the-github-graphql-api/