Обработка исключений при модернизации сетевых ошибок - PullRequest
1 голос
/ 23 октября 2019

Мне было интересно, как лучше всего обрабатывать сетевые ошибки в запросах на модификацию при использовании сопрограмм.

Классическим способом является обработка исключений на самом высоком уровне, когда сделан запрос:

try {
    // retrofit request
} catch(e: NetworkException) {
    // show some error message
}

Я считаю это решение неправильным и оно добавляет много стандартного кода, вместо этого я пошел на созданиеперехватчик, который возвращает ответ об ошибке:

class ErrorResponse : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        return try {
            chain.proceed(request)
        } catch (e: Exception) {
            Snackbar.make(
                view,
                context.resources.getText(R.string.network_error),
                Snackbar.LENGTH_LONG
            ).show()
            Response.Builder()
                .request(request)
                .protocol(Protocol.HTTP_1_1)
                .code(599)
                .message(e.message!!)
                .body(ResponseBody.create(null, e.message!!))
                .build()
        }
    }
}

Это решение немного лучше, но я думаю, что оно может быть улучшено.

Итак, мой вопрос: как правильнообрабатывать случаи, когда у пользователя нет подключения к Интернету, без большого количества стандартного кода (в идеале с глобальным обработчиком в случае ошибок подключения)?

Ответы [ 2 ]

1 голос
/ 29 октября 2019

Использование Result для переноса моего ответа

sealed class Result<out T : Any> {
data class Success<out T : Any>(val value: T) : Result<T>()
data class Failure(val errorHolder:ErrorHolder) : Result<Nothing>()}

ErrorHolder:

sealed class ErrorHolder(override val message):Throwable(message){
 data class NetworkConnection(override val message: String, val throwable: Throwable) : ErrorHolder(message)
 data class BadRequest(override val message: String, val throwable: Throwable) : ErrorHolder(message)
}

расширение для обработки исключений

suspend fun <T, R> Call<T>.awaitResult(map: (T) -> R): Result<R> = suspendCancellableCoroutine { continuation ->
try {
    enqueue(object : Callback<T> {
        override fun onFailure(call: Call<T>, throwable: Throwable) {
            errorHappened(throwable)
        }

        override fun onResponse(call: Call<T>, response: Response<T>) {
            if (response.isSuccessful) {
                try {
                    continuation.resume(Result.Success(map(response.body()!!)))
                } catch (throwable: Throwable) {
                    errorHappened(throwable)
                }
            } else {
                errorHappened(HttpException(response))
            }
        }

        private fun errorHappened(throwable: Throwable) {
            continuation.resume(Result.Failure(asNetworkException(throwable)))
        }
    })
} catch (throwable: Throwable) {
    continuation.resume(Result.Failure(asNetworkException(throwable)))
}

continuation.invokeOnCancellation {
    cancel()
}}

И вот как я делаю APIЗвоните:

suspend fun fetchUsers(): Result<List<User>> {
    return service.getUsers().awaitResult { usersResponseDto ->
        usersResponseDto.toListOfUsers()
    }
}
0 голосов
/ 23 октября 2019

Реализуя Interceptor, вы на правильном пути. Но с небольшими изменениями вы можете использовать этот пример класса:

class NetworkConnectionInterceptor(val context: Context) : Interceptor {

    @Suppress("DEPRECATION")
    private val isConnected: Boolean
        get() {
            var result = false
            val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                cm?.run {
                    cm.getNetworkCapabilities(cm.activeNetwork)?.run {
                        result = when {
                            hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                            hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                            hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                            else -> false
                        }
                    }
                }
            } else {
                cm?.run {
                    cm.activeNetworkInfo?.run {
                        if (type == ConnectivityManager.TYPE_WIFI) {
                            result = true
                        } else if (type == ConnectivityManager.TYPE_MOBILE) {
                            result = true
                        }
                    }
                }
            }
            return result
        }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        if (!isConnected) {
            // Throwing your custom exception
            // And handle it on onFailure
        }

        val builder = chain.request().newBuilder()
        return chain.proceed(builder.build())
    }
}

Затем добавить его к вашему OkHttpClient.Builder():

.addInterceptor(NetworkConnectionInterceptor(context));

И в случае неудачи вы можете обработать его в onFailureтакой метод:

override fun onFailure(call: Call<BaseModel>, t: Throwable) {
    if (t is NoConnectivityException) {
        // Handle it here :)
    }
}
...