Edited at

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


はじめに

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


サーバーサイド側

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

rubyでGraphQLを使うには、graphql-rubyというgemがあり、Gemfileに追加するだけで使える様になります。


Gemfile

gem 'graphql'


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

rails g graphql:install

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

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


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

などがオブジェクトを定義しているファイルです。


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



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


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

image.png

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


Android側

Androidアプリ側でAPIの呼び出しをするとき、これまでのRESTfulなAPIであれば、Retrofit2などのライブラリを用いることが多いでしょう。GraphQLは情報を取得するときもPOSTですが、当初はRetrofit2でPOSTを実装すれば良いと思っていました。しかし、GraphQLでは、apolloというのを用いるのが多い様です。今回もapolloを用いてGraphQLのClient側の実装を行いました。

導入についてはApolloのチームがandroid向けのドキュメントを整備してくれています(こちら)。ただ、ちょっと情報がわかりにくく、少し苦労しましたので、自分の備忘録も込めて記述したいと思います。

基本的にはこちらのGet startedに沿って説明します。


必要なpluginをbuild.gradleに追加

まず必要なpluginをbuild.gradleに追加します。プロジェクトディレクトリ直下にあるbuild.gradleのdependenciesに以下のclasspathを追加します。

mavenを見るとapollo-gradle-plugingradle-pluginの二つがありますが、apollo-gradle-pluginの方を使用します。


build.gradle

  buildscript {

dependencies {
// 省略
+ classpath 'com.apollographql.apollo:apollo-gradle-plugin:0.5.0'
}
}

app配下にあるbuild.gradleの方に次の2行を追加します。apply plugin: 'com.apollographql.android'に関してはAndroid Pluginの下に書く必要があるので、apply pluginの一番下に追加すれば良いでしょう。


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コマンドが未インストールの方は

yarn global add apollo-codegen

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

$ apollo-codegen --version

0.20.2

次にschema.jsonをダウンロードします。

apollo-codegen download-schema https://hogehoge.com/graphql --output schema.json

schema.jsonは私の場合、

app/src/main/graphql/com/hogehoge/schema.json

に置きましたが、.graphqlと同じディレクトリに置く必要があります。2


.graphqlファイルの作成

今回は、以下の様な形で実装しました。


Category.graphql

query Category($id: ID!, $num: Int) {

category(id: $id) {
id
name
movies(num: $num) {
id
title
publishedAt
}
}
}

このファイルを元にpluginがjavaクラスを生成してくれます。このときのクラス名はCategoryQueryとなります。

1行目にquery Category(・・・) {と書いてありますが、そこの名称+Queryという形になります。

かっこの中は引数です。今回の場合、CategoryのID($id)と紐づく動画のとってくる数($num)を変数にしています。

このクエリが間違っているとコンパイル時にエラーになるので、GraphiQLなどで正しいかどうかを確認しておくと良いかもしれません。GraphiQLでは、左下のQUERY VARIABLESで変数をセットできます。

image.png

ここで余談ですが、moviesのところはResponseField.forListというメソッドで実装されるのですが、v0.4.1では未実装で、この様な形はもしかしたら当時はうまくいかなかったかもしれません。v0.4.2からはある様です。


利用する側での実装

okHttpClientとapolloClientを用意するところは、(参考)の通りです。

apolloClient.queryの引数は先ほど作成した.graphqlファイルになります。まだビルドしていない場合は、CategoryQueryクラスが生成されていないのでエラーになるかもしれませんので、.graphqlファイルを追加したタイミングでビルドしてみると良いかもしれません。

.graphqlファイルで定義した引数のところは、メソッドになるようです。

CategoryQuery.builder()

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

    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でもschema:downloadができる様なのですが、Android Studioでコンパイルしたときに、GraphQL schema file should contain a valid GraphQL introspection query resultのエラーがでてうまくいきませんでした。 



  2. schema.jsonのpathを明示することも可能な様です。(参考)