Retrofit+RxJava+kotlinを使ったAPIコール処理を作ってみた

# Retrofit+RxJava+kotlinを使ったAPIコール処理を作ってみた

## はじめに
AndroidでWebAPIを呼び出す処理を行う場合、いろいろ方法がありますが、今回はRetrofit+RxJavaを使用して簡単サンプルアプリを作成してみます。

### Kotlinの導入
Android Studio 3.2を使用しているのでkotlinの導入は自動化されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/57275/00095e11-a56e-a949-20cd-ffe5577d8591.png)

include Kotlin support にチェックをつけます。
そのままNextと進んで最終的に新規プロジェクトを作成すると


```build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.2.71'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
```

```app/build.gradle
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    :
}

dependencies {
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    :
    :
}

```

のように設定されるので、Kotlinが使用可能となります。



### RxJavaの導入


```app/build.gradle
    implementation "io.reactivex:rxjava:1.3.8"
    implementation "io.reactivex:rxandroid:1.2.1"

```

### Retrofit2の導入

```app/build.gradle
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
```

## APIを呼び出してみる

皆様お世話になっている定番の郵便番号から住所を検索できるAPI
http://zipcloud.ibsnet.co.jp/api/search
を使用しました。
[ZipCloud](http://zipcloud.ibsnet.co.jp/doc/api)さんありがとうございます :bow_tone1:

### ZipResponse（Response用のEntity オブジェクト）

APIを呼び出したときのレスポンスを格納するものになります。

```ZipResponse.kt
data class ZipResponse(
        var message: String? = null,
        var status: Int? = null,
        var results: ArrayList<Address> = ArrayList()
)
```
```Address.kt
data class Address(
        var address1: String ?= null, //"address1": "北海道",
        var address2: String ?= null, //"address2": "美唄市",
        var address3: String ?= null, //"address3": "上美唄町協和",
        var kana1: String ?= null, //"kana1": "ﾎｯｶｲﾄﾞｳ",
        var kana2: String ?= null, //"kana2": "ﾋﾞﾊﾞｲｼ",
        var kana3: String ?= null, //"kana3": "ｶﾐﾋﾞﾊﾞｲﾁｮｳｷｮｳﾜ",
        var prefcode: String ?= null, //"prefcode": "1",
        var zipcode: String ?= null //"zipcode": "0790177"
)
```
### ApiClientを作成

```ApiClient.kt
interface ApiClient {
    @GET("api/search")
    fun getZipCode(@Query("zipcode") zipcode: String): Observable<ZipResponse>
}
```
今回は１つのAPIコールだけですが、複数ある場合は、ここにAPI毎のメソッドを定義していきます
もちろん、`@GET`、`@POST`、その他もサポートされています。
https://square.github.io/retrofit/

### ApiClientManagerを作成

```ApiClientManager.kt
open class ApiClientManager {
    companion object {
        private const val ENDPOINT = "http://zipcloud.ibsnet.co.jp/"
        private val TAG = ApiClientManager::class.simpleName

        val apiClient: ApiClient
            get() = Retrofit.Builder()
                    .client(getClient())
                    .baseUrl(ENDPOINT)
                    .addConverterFactory(GsonConverterFactory.create(Gson()))
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build()
                    .create(ApiClient::class.java)

        private fun getClient(): OkHttpClient {
            return OkHttpClient
                    .Builder()
                    .connectTimeout(120, TimeUnit.SECONDS)
                    .readTimeout(120, TimeUnit.SECONDS)
                    .addInterceptor(HttpLoggingInterceptor().apply {
                        level = HttpLoggingInterceptor.Level.BODY
                    })
                    .build()
        }
    }
}
```

### 呼び出してみる

```MainActivity.kt

    private val compositeSubscription = CompositeSubscription()

    override fun onCreate(savedInstanceState: Bundle?) {
        binding.buttonSubmit.setOnClickListener { _ ->
            val zipcode = "0790177"
            compositeSubscription.clear()
            compositeSubscription.add(
                        ApiClientManager.apiClient.getZipCode(zipcode)
                                .subscribeOn(Schedulers.io())
                                .observeOn(AndroidSchedulers.mainThread())
                                .doOnNext {
                                    Log.d(TAG, "response=$it")
                                    binding.textViewResponse.text = Gson().toJson(it)
                                }
                                .doOnError {
                                }
                                .doOnCompleted {
                                }
                                .subscribe())
        }
    }

    override fun onDestroy() {
        compositeSubscription.clear()
        super.onDestroy()
    }
```
`onDestroy()` で `compositeSubscription.clear()` を呼ぶことでActivity破棄時に未処理のsubscribeを破棄できます

```Logcat
 D/OkHttp: --> GET http://zipcloud.ibsnet.co.jp/api/search?zipcode=0790177
 D/OkHttp: --> END GET
 D/OkHttp: <-- 200 OK http://zipcloud.ibsnet.co.jp/api/search?zipcode=0790177 (598ms)
 D/OkHttp: Content-Type: text/plain; charset=utf-8
 D/OkHttp: X-Cloud-Trace-Context: fbdffd1b9974a560f3a891322b559248
 D/OkHttp: Date: Fri, 28 Sep 2018 14:07:26 GMT
 D/OkHttp: Server: Google Frontend
 D/OkHttp: Content-Length: 854
 D/OkHttp: {
 D/OkHttp: 	"message": null,
 D/OkHttp: 	"results": [
 D/OkHttp: 		{
 D/OkHttp: 			"address1": "北海道",
 D/OkHttp: 			"address2": "美唄市",
 D/OkHttp: 			"address3": "上美唄町協和",
 D/OkHttp: 			"kana1": "ﾎｯｶｲﾄﾞｳ",
 D/OkHttp: 			"kana2": "ﾋﾞﾊﾞｲｼ",
 D/OkHttp: 			"kana3": "ｶﾐﾋﾞﾊﾞｲﾁｮｳｷｮｳﾜ",
 D/OkHttp: 			"prefcode": "1",
 D/OkHttp: 			"zipcode": "0790177"
 D/OkHttp: 		},
 D/OkHttp: 		{
 D/OkHttp: 			"address1": "北海道",
 D/OkHttp: 			"address2": "美唄市",
 D/OkHttp: 			"address3": "上美唄町南",
 D/OkHttp: 			"kana1": "ﾎｯｶｲﾄﾞｳ",
 D/OkHttp: 			"kana2": "ﾋﾞﾊﾞｲｼ",
 D/OkHttp: 			"kana3": "ｶﾐﾋﾞﾊﾞｲﾁｮｳﾐﾅﾐ",
 D/OkHttp: 			"prefcode": "1",
 D/OkHttp: 			"zipcode": "0790177"
 D/OkHttp: 		},
 D/OkHttp: 		{
 D/OkHttp: 			"address1": "北海道",
 D/OkHttp: 			"address2": "美唄市",
 D/OkHttp: 			"address3": "上美唄町",
 D/OkHttp: 			"kana1": "ﾎｯｶｲﾄﾞｳ",
 D/OkHttp: 			"kana2": "ﾋﾞﾊﾞｲｼ",
 D/OkHttp: 			"kana3": "ｶﾐﾋﾞﾊﾞｲﾁｮｳ",
 D/OkHttp: 			"prefcode": "1",
 D/OkHttp: 			"zipcode": "0790177"
 D/OkHttp: 		}
 D/OkHttp: 	],
 D/OkHttp: 	"status": 200
 D/OkHttp: }
 D/OkHttp: <-- END HTTP (854-byte body)
D/MainActivity: response=ZipResponse(message=null, status=200, results=[Address(address1=北海道, address2=美唄市, address3=上美唄町協和, kana1=ﾎｯｶｲﾄﾞｳ, kana2=ﾋﾞﾊﾞｲｼ, kana3=ｶﾐﾋﾞﾊﾞｲﾁｮｳｷｮｳﾜ, prefcode=1, zipcode=0790177), Address(address1=北海道, address2=美唄市, address3=上美唄町南, kana1=ﾎｯｶｲﾄﾞｳ, kana2=ﾋﾞﾊﾞｲｼ, kana3=ｶﾐﾋﾞﾊﾞｲﾁｮｳﾐﾅﾐ, prefcode=1, zipcode=0790177), Address(address1=北海道, address2=美唄市, address3=上美唄町, kana1=ﾎｯｶｲﾄﾞｳ, kana2=ﾋﾞﾊﾞｲｼ, kana3=ｶﾐﾋﾞﾊﾞｲﾁｮｳ, prefcode=1, zipcode=0790177)])
```

## 最後に
CompositeSubscriptionが便利です。
AsyncTaskを使用した場合などに比べ、シンプルにコードが書けると思います。

ソースは
https://github.com/motomiya326/retrofit-example
にあるのでご自由にお使いください。