Backlog APIを使えば、クライアントアプリケーションがBacklogの課題を閲覧・更新したり、プロジェクトを管理したりできるようになります。例えば、AndroidアプリをBacklog APIと連携させることで、アプリから課題の作成・編集ができるようになります。
今のところAPIとやりとりする公式な手段は、Javaのライブラリであるbacklog4jを使う方法があります。しかし、公式ライブラリを経由しないでAPIを使う場合や、APIの一部の機能だけを必要としている場合は、Retrofitライブラリの助けを借りることができます。本ブログでは、Android アプリの作成を目的に、ライブラリは Retrofit 2 、APIは Backlog API 、プログラミング言語に Kotlin を用いた場合の手順を解説します。
Retrofitとは
Retrofitは型安全なHTTPクライアントライブラリで、JavaやKotlinやAndroidからできるだけ楽しくREST APIを使えるようにするために作られました。これを使うとBacklog APIからJSONデータを読み出したり利用したりするのがだいぶ簡単になります。
この記事では、シンプルなAndroidアプリをKotlinで作ります(前回のGoogle I/O 2017でGoogleはKotlinをAndroidアプリを作るための公式言語の一つとしました)。
Backlog APIの認証と認可
Backlog APIを使うには、認証と認可を扱う必要があります。アプリからそれらを扱うには2つの方法があります。APIキーを使うやり方と、OAuth2認可を使うやり方です。詳しくはBacklog APIのドキュメントを参照してください。
このサンプルアプリでは、APIキーを使ったやり方でBacklog APIにアクセスします。このAPIキーはリクエストごとにクエリパラメータとして使用します。例えばこんな感じになります:
https://xx.backlogtool.com/api/v2/users/myself?apiKey=abcdefghijklmn
ご自分のBacklogスペースにログインして、個人設定にあるAPIページ(下の画像を参照)を開いてください。
そこでAPIキーを生成して、この後のサンプルコードに貼り付けてください。
詳しくはユーザーガイドのAPIの設定を参照してください。
サンプルアプリケーション
さて、APIキーを手に入れたので、次のステップに進みましょう。Android Studio 3.0をダウンロードして実行してください。Kotlinサポートははじめから使えるので、自分で何かをセットアップする必要はありません。新しいAndroidプロジェクトをEmpty Activityテンプレートから作成しましょう。デフォルトでKotlinが使えるようになっているはずです。また、AndroidManifest.xmlにてINTERNETのパーミッションを追加するのを忘れないようにしてください。
<uses-permission android:name=”android.permission.INTERNET”/>
Retrofitを依存ライブラリに追加する
次に、Gradleのビルドファイルにいくつかの依存ライブラリを追加する必要があります。
- コアRetrofit
- com.squareup.retrofit2:retrofit:2.3.0
- JSONからモデルへのコンバータ
- com.squareup.retrofit2:converter-moshi:2.3.0
- ロギング
- com.squareup.okhttp3:logging-interceptor:3.9.0
ロギングは必須ではありません。デバッグ目的でHTTP通信の内容を見る必要があるなら、これを依存ライブラリに入れておくと便利でしょう。
API呼び出しの結果をもっぱらJSON文字列として欲しいのであれば、JSONコンバータは不要です。
これで準備はすべて整いました。それでは早速取り掛かりましょう。
Retrofitを使う
Retrofitを使ってはじめてのリクエストを送信する前に、以下の準備が必要です:
- Backlog APIエンドポイントの定義: HTTPメソッド、URL、パラメータをKotlinのインターフェイスとして表したもの
- モデル:JSONで返される結果をKotlinで扱うためのdataクラス
- Backlog APIエンドポイントの実装:ビルダー関数を通じてRetrofitの設定をして、上記のインターフェイスで定義されたAPIエンドポイントの実装を作成します
APIエンドポイント
Backlog APIで自分のログイン情報を取得するリクエストを実装することから始めましょう。このAPIエンドポイントの詳細はBacklog APIのドキュメントをご覧ください。
Backlog APIのドキュメントから、次のことがわかります:
- APIのパスは/api/v2/users/myself
- メソッドはGET
- APIキーはクエリパラメータの一部
- レスポンスボディはJSON形式
{ "id": 1, "userId": "admin", "name": "admin", "roleType": 1, "lang": "ja", "mailAddress": "eguchi@nulab.example" }
これらの情報を使い、インターフェイスを次のように定義します:
import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query interface BacklogAPIService { @GET("api/v2/users/myself") fun myself(@Query("apiKey") apiKey: String): Call<User> }
インターフェイスのメソッドのアノテーションとそのパラメータは、Retrofitがリクエストをどのように取り扱うのかを示します。
Backlog APIのドキュメントにもとづき、myselfという名前の関数を作り、アノテーションでそれがGETリクエストであることを示し、アノテーションのパラメータでそのリソースの相対URLが”api/v2/users/myself”であると指定しています。APIキーは関数のパラメータとして渡し、戻り値はRetrofitのCallオブジェクトです。Callクラスは型パラメータを取り、私たちのケースではUserモデルがその型となります。関数とモデルの名前はなんでも構いません。ほかのエンドポイントのパラメータやレスポンスについてはBacklog APIのドキュメントを参照してください。
モデル
たいていは、生のJSONの結果を取り扱う代わりに、JSON文字列をインターフェイスで指定したモデルに変換します。上のメソッド例では、myself関数の呼び出し結果をUserモデルとして受け取ることができます。もしレスポンスを特定の型で受け取る必要がなければ、戻り値を単にCall<ResponseBody>とすることもできます。
先にGradleのビルドファイルに書いておいたMoshiライブラリを使って、Retrofitが自動的に生のJSONをUserモデルに変換してくれます。私たちはすべてのフィールドは必要ではないので、簡略化したバージョンのUserモデルを次のように定義します。
data class User(val id: Int, val name: String)
Retrofitの作成
いよいよ最後のピースです。作成関数を通してRetrofitオブジェクトの設定をおこない、APIエンドポイントの実際の実装を作ります。BacklogAPIServiceインターフェイスの上にcreateServiceというグローバル関数を追加しましょう。
fun createService(): BacklogAPIService { //Replace the space name with your own space name //https://myspacename.backlogtool.com/ //https://myspacename.backlog.jp/ val BASE_URL = "https://myspacename.backlog.jp/" val retrofit = Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create()) .baseUrl(BASE_URL) .build() return retrofit.create(BacklogAPIService::class.java) }
BASE_URLはご自身のBacklogスペースのURLに変更してください。
以上で全部です。必要なピースすべてが出揃いました。いま手元には、エンドポイントの定義とRetrofitオブジェクトの作成処理が書かれたファイルと、モデルを定義したファイルがあります。
APIリクエスト
Retrofitはネットワークへのリクエストを同期的にも非同期的にもできます。このサンプルでは、Callクラスのenqueueメソッドを使って非同期でのリクエストをすることにします。同期的なリクエストをする場合は代わりにexecuteメソッドを使います。
MainActivityクラスを以下のように変更してください:
import android.os.Bundle import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity import com.nulabinc.sample.retrofitblog.client.createService import com.nulabinc.sample.retrofitblog.models.User import retrofit2.Call import retrofit2.Callback import retrofit2.Response class MainActivity : AppCompatActivity() { private val API_KEY = “__YOUR__BACKLOG__API_KEY__” private val backlogApiService by lazy { createService() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) fetchMyData() } private fun fetchMyData() { backlogApiService.myself(API_KEY).enqueue(object : Callback { override fun onFailure(call: Call?, t: Throwable?) { } override fun onResponse(call: Call?, response: Response) { if (response.isSuccessful) { response.body()?.let { AlertDialog.Builder(this@MainActivity) .setTitle("My Data") .setMessage(it.toString()).show() } } } }) } }
API_KEYを貼り付け、アプリを実行してください。
おめでとうございます。これで実際に動作するシンプルなBacklogクライアントアプリができました。
ロギング
はじめにbuild.gradleファイルにロギング用の依存ライブラリを追加していました。ここで、それを使ってみましょう。リクエストやレスポンスでどのようなデータがやりとりされているのかを見たければ、OkHttpClientビルダーにインターセプタを追加してロギングできるようにしてやります。
実際には、元のcreateService関数に対して二つのことしてやる必要があります。一つは、Retrofit.BuilderのデフォルトのOkHttpClientインスタンスの代わりに、こちらで用意したものを使うようにします。OkHttpはRetrofitが依存している低レベルのネットワークライブラリです。
二つ目に、OkHttpClient.Builder.addInterceptorメソッドを使ってHttpLoggingInterceptorインスタンスを追加してやります。HttpLoggingInterceptor.setLevelメソッドを使うとログの情報量を変更できます。
val httpLogging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) val httpClientBuilder = OkHttpClient.Builder().addInterceptor(httpLogging) val retrofit = Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create()) .baseUrl(BASE_URL) .client(httpClientBuilder.build()) .build()
アプリを実行すると、Android Studioの下部にあるlogcatタブでログ出力を確認できます。
インターセプタ
インターセプタは強力な仕組みで、呼び出しを監視したり、変更したり、リトライしたりできます。上ではそれをロギングに使いました。インターセプタを使えば、BacklogのAPIキーをすべてのリクエストに自動的に差し込むこともできて、エンドポイントの定義がよりシンプルになります。
import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query interface BacklogService { @GET("api/v2/users/myself") fun myself(): Call<User> }
もう一度createServiceを修正して、APIキーをリクエストに差し込む新しいインターセプタを追加します。しかしまず、OkHttpのInterceptorインターフェイスを実装する新しいクラスApiKeyIntercepterクラスを作ります。
class ApiKeyInterceptor(private val apiKey: String) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val oldRequest = chain.request() val newHttpUrl = oldRequest.url().newBuilder().addQueryParameter("apiKey", apiKey).build() val newRequest = oldRequest.newBuilder().url(newHttpUrl).build() return chain.proceed(newRequest) } }
次に、このインターセプタのインスタンスを作ってAPIキーを渡します。
fun createService(): BacklogAPIService { //Replace the space name with your own space name //https://myspacename.backlogtool.com/ //https://myspacename.backlog.jp/ val API_KEY = "__YOUR__BACKLOG__API_KEY__" val BASE_URL = "https://myspacename.backlog.jp/" val httpLogging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) val apiKeyInterceptor = ApiKeyInterceptor(API_KEY) val httpClientBuilder = OkHttpClient.Builder() .addInterceptor(httpLogging) .addInterceptor(apiKeyInterceptor) val retrofit = Retrofit.Builder() .addConverterFactory(MoshiConverterFactory.create()) .baseUrl(BASE_URL) .client(httpClientBuilder.build()) .build() return retrofit.create(BacklogAPIService::class.java) }
アプリを実行する前に、MainActivityクラスからAPI_KEYを取り除いてください。
サービスジェネレータ
これまでのところ順調ですが、BacklogAPIServiceインターフェイスにもっとAPIのエンドポイントを追加しだすと、ある時点で、BacklogAPIServiceインターフェイスのメソッドが多すぎるのでリファクタリングが必要だと思うようになります。
これらのエンドポイントをいくつかのインターフェイスに分割することができるでしょう。例えば、ユーザー関連のリクエストのためのインターフェイス、課題関連のリクエストのためのインターフェイス、といった具合です。
Bark KiersさんのサンプルリポジトリにあるServiceGeneratorパターンを使い、新しく作ったインターフェイスから実装を生成するようにしてみましょう。
ServiceGeneratorを使うと、Retrofitを設定する責務がグローバル関数からこのクラスに移ります。このパターンを使うと、同じオブジェクト(OkHttpClientやRetrofitなど)がアプリ中で再利用され、キャッシングその他諸々含めて、一つのソケット接続ですべてのリクエストとレスポンスを処理するようになります。ソケット接続を再利用するためにOkHttpClientインスタンスを一つだけ使うようにするのはよくやる方法です。
object RetrofitServiceGenerator { private val API_KEY = "__YOUR__BACKLOG__API_KEY__" //Replace the space name with your own space name //https://myspacename.backlogtool.com/api/v2/ //https://myspacename.backlog.jp/api/v2/ private val BASE_URL = "https://myspacename.backlog.jp/" //We are using Moshi for JSON string resource mapping private val retrofitBuilder = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create()) private val apiKeyInterceptor = ApiKeyInterceptor(API_KEY) private val httpLogging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) private val httpClientBuilder = OkHttpClient.Builder() .addInterceptor(httpLogging) .addInterceptor(apiKeyInterceptor) private var retrofit = retrofitBuilder.client(httpClientBuilder.build()).build() fun createServiceFor(serviceClass: Class): T { return retrofit.create(serviceClass) } inline fun createService(): T { return createServiceFor(T::class.java) } }
これを使うには次のようにします:
val userApiService = RetrofitServiceGenerator.createService<UserApiService>() val issueApiService = RetrofitServiceGenerator.createService<IssueApiService>()
MainActivityクラスでこれを使うには次のようにします:
private val backlogApiService by lazy { RetrofitServiceGenerator.createService<BacklogAPIService>() }
以上のように、Retrofitを使ってBacklog APIを使うのは簡単です。上記のシンプルなBacklogAPIServiceインターフェイスから始めて、必要に応じてエンドポイントを簡単に追加できます。
Happy Backlogging!
(翻訳:Kim, Shimokawa, Rainbow)