Android
Kotlin
HttpClient

HTTPクライアントライブラリFuelを初めて使ってみた2 RXJava編

はじめに

HTTPクライアントライブラリFuelを初めて使ってみたの続編です。

Kotlin/Andorid用のHTTPクライアントライブラリであるFuelのRXJavaメソッドを試してみました。
https://github.com/kittinunf/Fuel
以降のコード例はKotlinです。

導入

Fuelの他にRX Java, GSONへをapp/build.gradleに追加します。

app/build.gradle
dependencies {
    ・・・
    // Fuel
    compile 'com.github.kittinunf.fuel:fuel:1.9.0'
    compile 'com.github.kittinunf.fuel:fuel-android:1.9.0'
    compile 'com.github.kittinunf.fuel:fuel-rxjava:1.9.0'
    // RX Java
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    // GSON 
    compile 'com.google.code.gson:gson:2.6.2'
    ・・・
}

実装

例として、Qiitaのタグ取得APIへGetリクエストを送ってみます。
https://qiita.com/api/v2/docs#タグ
例ではリクエストパラメータはページのみ指定します。
https://qiita.com/api/v2/tags?page=1
このGETリクエストのレスポンスは以下のようなJSONになります。

Response
[
    {
        "followers_count": 0,
        "icon_url": null,
        "id": "Cutter",
        "items_count": 1
    },
    {
        "followers_count": 0,
        "icon_url": null,
        "id": "ポインター",
        "items_count": 1
    }
    ・・・
]

https://qiita.com/api/v2/tags?page=a
のようにパラメータが不正であったりした場合、エラーレスポンスは以下のようなJSONになります。この場合ステータスコードは400です。

Error
{
    "message": "Bad request",
    "type": "bad_request"
}

アプリでこのタグデータとエラーレスポンスのJSONを格納するクラスをそれぞれ定義します。

Tag
// タグ
data class Tag(val followers_count: String, val icon_url: String, val id: String, val items_count: Int)
QiitaError
// エラー情報を格納
data class QiitaError(val message: String, val type: String)

APIのステータスコードが200以外の場合を例外として処理するための例外クラスを作成します。この例外クラスにAPIのエラー情報を格納します。

QiitaException
class QiitaException(val qiitaError: QiitaError) : Exception()

実際にAPIに対してリクエストを行うクラスを作成します。
Fuelのメソッドrx_responseString()が返す型はSingle<Pair<Response, Result<String, FuelError>>>ですが、
clean architecutreなどデータアクセス層を分離していくことを考え、 メソッドからはSingle<List<Tag>>を返しています。
ステータスコードが200の場合はsubscribe側のonSuccessで処理し、200以外の場合、エラーオブジェクトをsubscribe側のonErrorメソッドで処理します。このためにSingle.createとSigle.errorを使って返り値を作成します。

// データアクセス層のクラス
class Network {

    fun getTags(): Single<List<Tag>> {
        // 簡略化してパラメータは固定値
        return "https://qiita.com/api/v2/tags".httpGet(listOf("page" to "1")).rx_responseString().flatMap { p ->
            when (p.first.httpStatusCode) {
                // ステータス200は成功
                200 -> {
                    val gson = Gson()
            // タグ情報のJSONをオブジェクトへ変換
                    Single.create<List<Tag>> { e ->
                        e.onSuccess(gson.fromJson(p.second.get(), object: TypeToken<List<Tag>>(){}.type))
                    }
                }
                // 200以外はエラーとする
                else -> {
                    // エラー情報のJSONをオブジェクトへ変換し例外オブジェクトへ格納
                    val input = InputStreamReader(ByteArrayInputStream(p.first.data))
                    val gson = Gson()
                    val e2 = gson.fromJson(input, QiitaError::class.java)
                    Single.error(QiitaException(e2))
                }
            }
        }
    }
}

今回はアクティビティから呼び出します。この例では簡略化してNetworkクラスのインスタンス生成していますが、実際はDaggerでDIしたほうがいいと思います。

MainActivity
・・・
val network = Network()
network.getTags()
    // APIへのリクエストは別スレッドで処理
    .subscribeOn(Schedulers.newThread())
    // APIへの通信後はメインスレッドで処理
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        { 
           // onSuccess(List<TAG> -> Unit)
          res ->
            Log.i(TAG, res.joinToString(","))
            // 画面表示など
            ・・・

        },
        { 
            // onError
            // onError(Throwable -> Unit)なのでisで型チェック
            e ->
            if (e is QiitaException) {
                Log.i(TAG, e.qiitaError.toString())
                // エラーダイアログ表示など
                ・・・
            }

        }
    )
・・・

エラー処理はもっといいやり方があるかもしれません。何かご存知の方は教えてください。