読者です 読者をやめる 読者になる 読者になる

そーす

福岡在住のプログラマ。SwiftとかKotlinとかJavascriptとかSketchとか触ってます。

Retrofitで503の時にメンテナンスモードを表示する

f:id:saburesan:20160906091652j:plain

Kotlinは特に関係ないけど。

Retrofitって503ってデフォルトでリクエスト成功の処理に回されるんですかね?

私の環境だと503を成功として扱っていて困りました。

また、デフォルトで成功処理の場合はステータスコードが取得出来ないのでRetrofitでAPIのinterfaceの定義を変更する必要がありました。

ただ、もともとSchedulerのの設定などを共通で行うExtensionを作っていたので変更はすぐに出来ました。

以下変更点

・成功時もステータスコードを取得できるように

//変更前
@Headers("Accept: application/json")
@GET("setup")
fun getSetUp(): Single<SetupResponse>

//変更後 retrofit2のResponseクラスを使う
@Headers("Accept: application/json")
@GET("setup")
fun getSetUp(): Single<Response<SetupResponse>>

・Schedulerなどの共通処理

//変更前
fun <T> Single<T>.androidDefaultSubscribe(subscriber: ApiSubscriber<T>): Subscription {
    return observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(subscriber)

//変更後
fun <T> Single<Response<T>>.androidDefaultSubscribe(subscriber: ApiSubscriber<T>): Subscription {
    return observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(object : SingleSubscriber<Response<T>>() {
        override fun onSuccess(value: Response<T>?) {
            value?.let {
                if (it.isSuccessful) {
                    subscriber.onSuccess(it.body())
                } else {
                    onError(error = HttpException(it))
                }
            } ?: onError(error = null)
        }

        override fun onError(error: Throwable?) {
            subscriber.onError(error)
        }
    })
}

・エラーメッセージを整形する処理 SingleSubscriberを継承した抽象クラスです。 レスポンスがエラーの場合はエラーメッセージを配列で受け取るのでそれらを表示する形式に整形して返します。 statusCodeが503の場合にメンテナンス用Activityを別タスクで起動して処理を終了させます。

abstract class ApiSubscriber<T> : SingleSubscriber<T>() {
    override fun onError(error: Throwable?) {
        error?.printStackTrace()
        val httpException = error as? HttpException
        val statusCode = httpException?.code() ?: 0
        val errorMessage = when (statusCode) {
            0 -> App.context.getString(R.string.network_error)
            503 -> {
                MaintenanceActivity.start()
                return
            }
            else -> httpException?.response()?.errorBody()?.string()?.let { GsonWrapper.gson.fromJson(it, ErrorMessagesResponse::class.java)?.errors?.reduce { s1, s2 -> "$s1\n$s2" } }
                    ?: App.context.getString(R.string.network_error)
        }
        onError(ErrorResponse(statusCode = statusCode, message = errorMessage))
    }

    abstract fun onError(errorResponse: ErrorResponse)
}

・メンテナンス用Activity

class MaintenanceActivity : AppCompatActivity() {
    private val view by lazy {
        val webView = WebView(this)
        webView.setWebViewClient(WebViewClient())
        webView
    }

    companion object {
        fun start() {
            val intent = Intent(App.context, MaintenanceActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            App.context.startActivity(intent)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(view)
        view.loadUrl(MAINTENANCE_URL)
    }
}