[Android] Retrofit +RxJava(Kotlin)でAPI通信を行う

この手の記事、何番煎じだよ!って感じだけど。。。

なにげに自分で1から組んだことなかったから経験値が欲しくてやってみた。

素のRetrofitを使うよりRxを噛ませたほうがコールバックとかが楽だしね!

この実装が正解かはよくわからないが、将来自分が簡単に実装できるようにコピペ出来るようにしておく!w

スポンサーリンク

実装

依存

    // RX
    implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'

    // Retrofit
    def retrofit = "2.5.0"
    def moshi = "1.8.0"
    implementation "com.squareup.retrofit2:retrofit:$retrofit"
    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofit"
    implementation "com.squareup.moshi:moshi:$moshi"
    implementation "com.squareup.moshi:moshi-kotlin:$moshi"
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    implementation "com.squareup.okhttp3:okhttp:3.12.0"
    implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'

    // Log
    implementation "com.jakewharton.timber:timber:4.7.1"

ConverterはMoshiで。Moshiの使い勝手が未だにあまりわかってないけど、、、。Gsonでよくね?w

通信内容を見たいからstethoも入れておく。

TimberDebugTree入れておくとログがとっても見やすく!(今回はやらないけど)

API基底クラス

// API基底
abstract class ApiClient {

    abstract val baseUrl: String

    // Retrofitのインスタンス
    val retrofit: Retrofit by lazy {
        getBaseRetrofit()
            .baseUrl(baseUrl)
            .client(requireNotNull(getOkHttpClientBuilder(baseOkHttp).build()))
            .build()
            .let { requireNotNull(it) }
    }

    // 基底となるRetrofit
    private fun getBaseRetrofit(): Retrofit.Builder = Retrofit.Builder()
        .addConverterFactory(getConverterFactory())
        .addCallAdapterFactory(geAdapterFactory())

    // 基底となるOkHttp
    private val baseOkHttp = getCommonHttpClientBuilder()
        .addInterceptor(Interceptor { chain ->
            val original = chain.request()
            //header
            val request = original.newBuilder()
                .header("Accept", "application/json")
                .method(original.method(), original.body())
                .build()

            return@Interceptor chain.proceed(request)
        })
        .addInterceptor { it.proceed(addUserAgent(it.request())) }
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)


    // OkHttpを改変用(Interceptorを個別設定したいときとか)
    protected open fun getOkHttpClientBuilder(base: OkHttpClient.Builder): OkHttpClient.Builder =
        base

    // ConverterFactoryを改変用(Moshi,Gsonとか)
    protected open fun getConverterFactory(): Converter.Factory = MoshiConverterFactory.create(
        Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()
    )

    // AdapterFactory改変用(Rx,Coroutinesとか)
    protected open fun geAdapterFactory(): CallAdapter.Factory = RxJava2CallAdapterFactory.create()


    private fun getCommonHttpClientBuilder(): OkHttpClient.Builder {
        return if (BuildConfig.DEBUG) {
            OkHttpClient.Builder()
                .addInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
                    Timber.tag("OkHttp").d(it)
                }).apply {
                    level = HttpLoggingInterceptor.Level.BODY
                })
                .addNetworkInterceptor(StethoInterceptor())
        } else {
            OkHttpClient.Builder()
        }
    }

    private fun addUserAgent(req: Request): Request {
        val agent = "AppName/" + BuildConfig.VERSION_NAME + " Android/" + BuildConfig.VERSION_NAME
        return req.newBuilder().header("User-Agent", agent).build()
    }
}

とりあえず、複数のサービスのAPIを叩く想定。

あとRxじゃないパターンも有るのかな?と、、、

てかCoroutinesでやってみたいから一旦この形で(時間あれば Coroutinesで試して次の記事で書く予定)

APIクラス

// Rxで通信するAPIクラス
class RxApiClient : ApiClient() {

    override val baseUrl: String = "https://randomuser.me/"

    val service: RxApiService = retrofit.create(RxApiService::class.java)

}

基本的にApiClientを継承してBaseUrlを設定するだけ。

Interceptorを追加したいときはgetOkHttpClientBuilderをオーバーライドしてあげる。

ネットワーク監視とかあるといいかもね。

今回は以下のサービスを利用させてもらった。

Service

// Rx用のサービス
interface RxApiService {

    @GET("api")
    fun getRandomUser(): Single<RandomUser>

}

Singleで返すようにする。

使ってみる

//ViewModel
class MainViewModel : RxViewModel() {

    private val rxApiClient = RxApiClient()

    fun getRxDeRandomUser() {
        rxApiClient.service.getRandomUser()
            .subscribeOn(Schedulers.io())
            .map {
                val user = it.results[0]
                """name : ${user.name.title} - ${user.name.first} - ${user.name.last}
                    gender : ${user.gender}
                    email : ${user.email}
                    phone : ${user.phone}
                    cell : ${user.cell}
                    
                """.trimIndent()
            }.observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onSuccess = {
                    Log.d("hoge", "getRxDeRandomUser $it")
                },
                onError = {
                    Log.d("hoge", "onError getRxDeRandomUser $it")
                }

            ).addTo(disposables)

    }
}

なんとなくデータ加工して返すようにしてみた。

RxとRetrofit掛け合わせると結構スッキリ書けるよね!

あ、DataClass書かなかったけど、よしなに定義で!w

タイトルとURLをコピーしました