昔参考にさせていただいたflowのラッパーメソッド(apiFlow)で、エラーコードとエラー時のbodyを取得できるようにした。
ちなみに、このラッパーを採用した理由は元記事に書かれている通りで、UIレイヤに例外をそのまま伝搬させたくなかったから。
apiFlow.kt
inline fun <reified T : Any> apiFlow(crossinline call: suspend () -> Response<T>): Flow<Result<T>> =
flow<Result<T>> {
try {
val response = call()
if (response.isSuccessful) {
emit(Result.Success(value = response.body()!!))
} else {
val errorResponse = response.errorBody()?.let { errorBody ->
Gson().fromJson(errorBody.string(), ErrorResponse::class.java)
}
emit(Result.Error(errorCode = response.code(), errorResponse = errorResponse))
}
} catch (e: Exception) {
// 例外発生時は強制的に400エラーとする
emit(Result.Error(errorCode = 400, errorResponse = null))
}
}.onStart {
emit(Result.Proceeding)
}.flowOn(Dispatchers.IO)
Result.kt
sealed class Result<out T> {
// APIコールが実行中である
object Proceeding : Result<Nothing>()
// APIコールが成功した
data class Success<out T>(val value: T) : Result<T>()
// APIコールが失敗した
data class Error(val errorCode: Int?, val errorResponse: ErrorResponse?, val throwable: Throwable? = null) : Result<Nothing>()
}
ErrorResponseクラスは以下。
今回ErrorBodyの中身がjsonなのでパースしてる。
ErrorResponse.kt
data class ErrorResponse(
@SerializedName("messageId") val messageId: String?,
@SerializedName("errorMessage") val errorMessage: String?
)
レスポンスを取得する側、ViewModelの処理はこんな感じ
SomeViewModel.kt
someRepository.getSomething().collectLatest { result ->
when (result) {
is Result.Success -> {
}
is Result.Error -> {
logd("エラーコード:" + result.errorCode)
logd("エラーレスポンス:" + result.errorResponse)
// エラーハンドリング
when (result.errorCode) {
400 -> {
}
404 -> {
} //などなど
}
}
else -> {} //読込中状態が入る、何もしない
}
}
もうちょっと改良の余地がありそう。
GsonもMoshiに載せ替えたい。