【Android】Retrofit + Rx

## Retrofit

[公式ページ](https://github.com/square/retrofit)

RetrofitのRx化に関しては Jake神作の[Adapter](https://github.com/JakeWharton/retrofit2-rxjava2-adapter)を使っていたらしい。。が
現在本家のretrofitにAdapterが出来てるみたいで Jake神作のは`DEPRECATED `になっている。

[こちら](https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2)を使う:eyes:

###  :computer:環境構築
****

`app/build.gradle`に以下を追加

```gradle
    implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.1.0'
    implementation 'com.squareup.moshi:moshi:1.5.0'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
```
### :pencil: 実装
****

#### 簡単なサンプル

サンプルとしても多い、GithubのAPIを使ってユーザー情報を取得して見ます。

- Userクラス (ユーザー情報を格納するデータクラス)

    ```kotlin
    import com.squareup.moshi.Json
    
    data class User(
        @Json(name = "name")
        var name: String,
        @Json(name = "login")
        var login: String,
        @Json(name = "blog")
        var blog: String,
        @Json(name = "type")
        var type: String
    )
    ```

- GithubApiクラス (interfaceを定義)

    ```kotlin
    import io.reactivex.Observable
    import retrofit2.http.GET
    import retrofit2.http.Path
    
    interface GithubApi {
    
        companion object {
            const val BASE_URL = "https://api.github.com"
        }
    
        @GET("users/{username}")
        fun getUser(@Path("username") user: String): Observable<User>
    }
    ```

- MainActivityクラス

    ```kotlin
    class MainActivity : AppCompatActivity() {
    
        companion object {
            val TAG = MainActivity::class.java.simpleName
        }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val retrofit = Retrofit.Builder()
                    .baseUrl(GithubApi.BASE_URL)
                    .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build()))
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
                    .build()
    
            val service = retrofit.create(GithubApi::class.java)
            service.getUser("Slowhand0309").subscribe({ ret ->
                Log.d(TAG, "ret $ret")
            }, { error ->
                Log.e(TAG, error.message)
            })
        }
    }
    ```

AndroidManifestに
`<uses-permission android:name="android.permission.INTERNET"/>` を追加するのを忘れずに

### 戻り値に関して

RetrofitのRxJava2 Adapterは戻り値として
Observable, Single, Maybe, Completable
を提供している、が、それぞれの違いがわからないので調べてみる。

- Observable: いつも使っているので省力
- Single
    - onNext+onComplete = onSuccess でonSuccessは一回しか呼べない
- Maybe
    - onSuccessかonErrorかonCompleteのどれかが呼ばれる、または全く呼ばれません。
- Completable
    - onErrorかonCompleteのどれかが呼ばれる、または全く呼ばれません。


[参考URL](https://qiita.com/takahirom/items/f3e576e91b219c7239e7)

----
#### エラー処理に関して

200番台以外のレスポンスの場合 `HttpException` として `onError` が呼ばれる
[実際のソースコード](https://github.com/square/retrofit/blob/97057aaae42e54bfbee8acfa8af7dcf37e812342/retrofit-adapters/rxjava2/src/main/java/retrofit2/adapter/rxjava2/BodyObservable.java#L54)

エラー処理に関しては↓のように捌けば良さそう。

```kotlin
        disposable += ApiClient.createUser()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({ res ->
                    Log.d(TAG, "create user success ${res.id}")
                }, { error ->
                    (error as? HttpException)?.let {
                        Log.e(TAG, "code: ${it.code()}")
                        Log.e(TAG, "message: ${it.message()}")
                        Log.e(TAG, "response: ${it.response()}")
                    }
                })
```
401が返ってきた場合の実際の出力

```
E/MainActivity: code: 401
    message: 
E/MainActivity: response: Response{protocol=h2, code=401, message=, url=https://....}
```
###  :bomb: バッドノウハウ
****

単純にmoshiを使うだけだと、dataクラスのnon-nullのプロパティにnullが入ってしまう!
[参考URL](https://qiita.com/egugue/items/f1f35c250f7a25768751)

そこでmoshiのkotlin extensionを導入して、non-nullにnullを入れうようとしたら
例外を投げるように修正

`app/build.gradle`に以下を追加

```gradle
implementation 'com.squareup.moshi:moshi-kotlin:1.5.0'
```

`MainActivity`のmoshiを生成している箇所をいかに変更

```kotlin
        val moshi = Moshi.Builder()
                    .add(KotlinJsonAdapterFactory())
                    .build()

        val retrofit = Retrofit.Builder()
                .baseUrl(GithubApi.BASE_URL)
                .addConverterFactory(MoshiConverterFactory.create(moshi))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
                .build()
```
これでnon-null / null をきっちり管理してくれます:+1:

### :link: 関連リンク
****

- [Retrofit + Gson で、 API にアクセス & JSON のパージング処理を簡潔に実装する](https://qiita.com/Hachimori/items/c7349ec068924b7ce045)
- [参考にしたリポジトリ safe-android](https://github.com/gnosis/safe-android)
- [square/moshi](https://github.com/square/moshi)