はじめに
趣味でAndroidアプリを開発しているのですが、サーバーサイドとのやり取りをGraphQLを用いて行ってみようとしたときに、インターネット上の情報がやや古く、公式のページを見ても幾つか引っかかった箇所があったので、自分の備忘録的に記述します。
サーバーサイド側
普段はRailsのエンジニアをしているので、サーバーサイドはRailsを用いました。サーバー側の実装は興味ない方は読み飛ばしてください。
rubyでGraphQLを使うには、graphql-rubyというgemがあり、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
でアクセスできそうです。
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
などがオブジェクトを定義しているファイルです。
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
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
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 にアクセスして簡単なクエリを発行すればレスポンスが返ってきます。
簡単にサーバーサイド側の準備を行いました。今回は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-pluginとgradle-pluginの二つがありますが、apollo-gradle-pluginの方を使用します。
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
の一番下に追加すれば良いでしょう。
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ファイルの作成
今回は、以下の様な形で実装しました。
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で変数をセットできます。
ここで余談ですが、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などを用いればもう少しスッキリかけるかと思います。
参考
- Apollo GraphQL Client for Android
- APOLLO ANDROID GUIDE
- GitHub GraphQL API v4 を Android で遊んでみる
- Apollo-androidのレスポンスキャッシュを使ってみる
-
apollo-cliでも
schema:download
ができる様なのですが、Android Studioでコンパイルしたときに、GraphQL schema file should contain a valid GraphQL introspection query result
のエラーがでてうまくいきませんでした。 ↩