web-dev-qa-db-ja.com

コルーチンとレトロフィット、エラーを処理する最良の方法

この問題を読んだ後 例外の対処方法 とこの媒体 2019年のAndroidネットワーキング-Kotlinのコルーチンによるレトロフィット を作成できるBaseServiceで構成されるソリューションを作成しました呼び出しを改造して、結果と例外を「チェーン」に転送します。

[〜#〜] api [〜#〜]

@GET("...")
suspend fun fetchMyObject(): Response<List<MyObject>>

BaseService

protected suspend fun <T : Any> apiCall(call: suspend () -> Response<T>): Result<T> {
    val response: Response<T>
    try {
        response = call.invoke()
    } catch (t: Throwable) {
        return Result.Error(mapNetworkThrowable(t))
    }
    if (!response.isSuccessful) {
        val responseErrorBody = response.errorBody()
        if (responseErrorBody != null) {
            //try to parse to a custom ErrorObject
            ...
            return Result.Error...
        }
        return Result.Error(mapHttpThrowable(Exception(), response.raw().code, response.raw().message))
    }
    return Result.Success(response.body()!!)
}

ChildService

suspend fun fetchMyObject(): Result<List<MyObject>> {
    return apiCall(call = { api.fetchMyObject() })
}

レポ

    suspend fun myObjectList(): List<MyObject> {
        return withContext(Dispatchers.IO) {
            when (val result = service.fetchMyObject()) {
                is Result.Success -> result.data
                is Result.Error -> throw result.exception
            }
        }
    }

注:例外または1種類の成功結果をスローする以上のものが必要になる場合があります。これらの状況を処理するために、これは私たちがそれを達成する方法です。

sealed class SomeApiResult<out T : Any> {
    object Success : SomeApiResult<Unit>()
    object NoAccount : SomeApiResult<Unit>()
    sealed class Error(val exception: Exception) : SomeApiResult<Nothing>() {
        class Generic(exception: Exception) : Error(exception)
        class Error1(exception: Exception) : Error(exception)
        class Error2(exception: Exception) : Error(exception)
        class Error3(exception: Exception) : Error(exception)
    }
} 

そして、ViewModelで:

when (result: SomeApiResult) {
    is SomeApiResult.Success -> {...}
    is SomeApiResult.NoAccount -> {...}
    is SomeApiResult.Error.Error1 -> {...}
    is SomeApiResult.Error -> {/*all other*/...}
}

このアプローチの詳細 ここ

BaseViewModel

protected suspend fun <T : Any> safeCall(call: suspend () -> T): T? {
    try {
        return call()
    } catch (e: Throwable) {
        parseError(e)
    }
    return null
}

ChildViewModel

fun fetchMyObjectList() {
    viewModelScope.launch {
        safeCall(call = {
            repo.myObjectList()
            //update ui, etc..
        })
    }
}

ViewModel(またはBaseViewModel)は、例外を処理するレイヤーである必要があると思います。たとえば、トーストを表示したい場合、例外のタイプを無視したい場合、別の関数を呼び出した場合など、このレイヤーにはUI決定ロジックがあります。 ..

どう思いますか?

EDIT:このトピックで medium を作成しました

5
GuilhE

リポジトリで例外を処理し、封印されたクラスオブジェクトの形式で単一の応答をviewmodelに返すことをお勧めします。これをリポジトリに保持することで、リポジトリが単一の信頼できる情報源となり、コードがよりクリーンで読みやすくなります。

1
Ashok Kumar