KotlinでRetrofitとRxを使ってAPIクライアントをサクッと実装する


RetrofitはAPI Clientを作成するためのライブラリです。
インターフェースを定義するだけで作れるのでとても楽ちんです。

またRetrofitはRxJavaを使って非同期の処理をいい感じに書くようにすることもできますしHTTP通信にはOkHttpをJSONのパースにはmoshiを使ってAPIクライアントを実装します。

実際どうやって使うのかをやっていこうと思います。

環境

Mac OSX
Android Studio 2.0
Kotlin 1.0.0-beta-2423

Kotlin pluginをAndroid Studioに入れておいてください

プロジェクトの作成

とりあえずBlankActivityのあるプロジェクトを適当に作成します。
作成されたJavaソースコードををKotlinに変換します。
上のメニューからCode->Convert Java File to Kotlin Fileで変換できます。
app/build.gradleに以下を追加します

build.gradle
+apply plugin: 'kotlin-android'

dependencies {
    ...
+    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    compile 'io.reactivex:rxkotlin:0.24.100'
+    compile 'io.reactivex:rxandroid:1.0.1'
+    compile 'io.reactivex:rxjava:1.0.15'
+    compile 'com.squareup.okhttp:okhttp:2.0.0'
+    compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0'
+    compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
+    compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
+    compile 'com.squareup.retrofit:converter-moshi:2.0.0-beta2'

}

+buildscript {
+    ext.kotlin_version = '1.0.0-beta-2423'
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath "org.jetbrains.kotlin:kotlin-gradle-+plugin:$kotlin_version"
+        classpath "org.jetbrains.kotlin:kotlin-android-+extensions:$kotlin_version"
+    }
+}

とりあえずビルドが通るところまでは確認しておきましょう

実装

Retrofitのインターフェイスを作成します。
今回はRxを使うのが目的なのでインターフェイスもRx用にします。

interface SplatoonService {
    @GET("/schedule.json")
    fun schedule(): Observable<ScheduleResponse>
}

ポイントは戻り値がrx.Observableであることです。
ScheduleResponseはイカのようにしています

data class ScheduleResponse(
        var updateTime: Long,
        var schedule: List<Schedule>
) : Serializable

data class Schedule(
        var startTime: Long,
        var endTime: Long,
        var regular: Match,
        var ranked: Match
): Serializable

data class Match(
        var maps: List<Map>,
        var rulesJP: String = "ナワバリバトル",
        var rulesEN: String = "Turf War"
): Serializable

data class Map(
        var nameJP: String,
        var nameEN: String
): Serializable

APIクライアントのインスタンスを生成します。

fun client(): SplatoonService {
        val moshi = Moshi.Builder()
                .build()

        val okClient = OkHttpClient()

        val builder = Retrofit.Builder()
                .client(okClient)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(MoshiConverterFactory.create(moshi))
                .baseUrl("https://splatoon.ink")
                .build()

        return builder.create(SplatoonService::class.java)
}

使い方

使い方は次の通りです。

client().schedule().
       subscribeOn(Schedulers.newThread()).
       observeOn(AndroidSchedulers.mainThread()).
       subscribe(object : Subscriber<ScheduleResponse>(){
           override fun onNext(r: ScheduleResponse?) {
               ...
           }

           override fun onError(e: Throwable?) {
               ...
           }
       })

このコードはUIThreadで実行されることを想定して
.subscribeOn(Schedulers.newThread())で別スレッドで実行し
.observeOn(AndroidSchedulers.mainThread())でコールバックをMainThreadで実行されるようにしています。

冗長なところをちょっと良くしてみる

ただこれだとせっかくのkotlinなのにsubscribeのところが冗長になってしまいます。
ラムダ式使ってもっといい感じに書けるように拡張関数を使ってみました。

public fun <T> Observable<T>.onError(block : (Throwable) -> Unit): KSubscription<T> {
    return KSubscription(this).onError(block)
}

public fun <T> Observable<T>.onCompleted(block : () -> Unit): KSubscription<T> {
    return KSubscription(this).onCompleted(block)
}

public fun <T> Observable<T>.onNext(block : (T) -> Unit): KSubscription<T> {
    return KSubscription(this).onNext(block)
}

public fun Subscription.onError(block: (Throwable) -> Unit): Subscription {
    return this
}

public class KSubscription<T>(val observable: Observable<T>) {

    private var error: (Throwable) -> Unit = { throw it }
    private var completed: () -> Unit = {}
    private var next: (T) -> Unit = {}

    fun onError(block: (Throwable) -> Unit): KSubscription<T> {
        error = block
        return this
    }

    fun onCompleted(block: () -> Unit): KSubscription<T> {
        completed = block
        return this
    }

    fun onNext(block: (T) -> Unit): KSubscription<T> {
        next = block
        return this
    }

    fun subscribe(): Subscription = observable.subscribe(object : Subscriber<T>(){
            override fun onError(e: Throwable?) {
                if ( e == null ) {
                    return
                }

                error.invoke(e)
            }

            override fun onCompleted() {
                completed.invoke()
            }

            override fun onNext(t: T) {
                next.invoke(t)
            }
        })
}

さっきのコードをこんな風に書くことができます。

client().schedule().
       subscribeOn(Schedulers.newThread()).
       observeOn(AndroidSchedulers.mainThread()).
       onNext {
           ...
       }.
       onError {
           ...
       }.
       subscribe()

まとめ

というわけで僅かなコードでAPIクライアントを実装することができました。
まぁちゃんと使う場合はネットワークがつながってるかの確認やAccessTokenがあるかないか確認して すべてonErrorに入れたかったりすると思いますが
これをJavaでやろうとするとメソッドで一旦引数としてObservable渡さなきゃいけなかったりするし
メソッドチェーンを作ろうと思ったらClassを継承してラップしてみたいなことをしなきゃいけないのを
Kotlinならカジュアルにメソッドを追加できるのは便利ですね。