Сопрограммы и модернизация, лучший способ справиться с ошибками - PullRequest
0 голосов
/ 24 сентября 2019

После прочтения этой проблемы Как справиться с исключением и этой средой Android Networking в 2019 году - Модификация с помощью сопрограмм Kotlin's Я создал свое решение, которое состоит из BaseServiceвыполнения вызова модернизации и пересылки результатов и исключений по «цепочке»:

API

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

BaseService:

@Throws(BaseException::class)
protected suspend fun <T : Any> apiCall(call: suspend () -> Response<T>): T {
    val result: Result<T> = apiResult(call)        
    (return when (result) {
        is Result.Success -> result.data
        is Result.Error -> throw result.exception
    })
}

private suspend fun <T : Any> apiResult(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(mapHttpThrowable(Exception(), response.raw().code, response.raw().message))
    }
    return Result.Success(response.body()!!)
}

ChildService

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

Репо

suspend fun myObjectList(): List<MyObject> {
    return withContext(Dispatchers.IO) {
        service.fetchMyObject()
    }
}

ViewModel

fun fetchMyObjectList() {
    viewModelScope.launch {
        try {
            repo.myObjectList()
        } catch (e: Throwable) {
            parseError(e)
        } 
    }
}

Я думаю, что ViewModel (или BaseViewModel) должен быть уровнем, обрабатывающим исключения, потому что в этом слое лежит логика принятия решения UI, например, если мы просто хотим показать тост, игнорируем типза исключением вызова другой функции и т. д.

Как вы думаете?

1 Ответ

1 голос
/ 25 сентября 2019

Я думаю, что ViewModel (или BaseViewModel) должен быть уровнем, обрабатывающим исключения, потому что в этом слое лежит логика принятия решения UI, например, если мы просто хотим показать тост, игнорируем тип исключениявызов другой функции и т. д. *

Как вы думаете?

Конечно, вы правы.Сопрограмма должна срабатывать на ViewModel, хотя логика в Repository / Service.Вот почему Google уже создал специальный coroutineScope с именем viewModelScope , в противном случае это будет "repositoryScope".Также сопрограммы имеют приятную функцию обработки исключений, которая называется CoroutineExceptionHandler.Здесь все становится лучше, потому что вам не нужно реализовывать try{}catch{} блок:

val coroutineExceptionHanlder = CoroutineExceptionHandler{_, throwable -> 
    throwable.printStackTrance()
    toastLiveData.value = showToastValueWhatever()
}

Позже в ViewModel

coroutineScope.launch(Dispatchers.IO + coroutineExceptionHanlder){
      val data = serviceOrRepo.getData()
}

Конечно, вы все еще можете использоватьблок try/catch без CoroutineExceptionHandler, свободный для выбора.

Обратите внимание, что в случае Retrofit вам не нужен планировщик Dispatchers.IO, потому что Retrofit делает это за вас (начиная с Retrofit 2.6.0).

В любом случае, я не могу сказать, что статья плохая, но это не лучшее решение.Если вы хотите следовать руководству по статьям, вы можете проверить Преобразования в LiveData .

РЕДАКТИРОВАТЬ: Вам нужно больше знать, что сопрограммы небезопасны.Я имею в виду, что они могут вызывать утечки памяти, особенно в жизненном цикле Android в целом.Вам нужен способ отменить сопрограммы, пока Activity / Fragment больше не живет.Так как ViewModel имеет onCleared (который вызывается, когда Activity / Fragment уничтожен), это означает, что сопрограммы должны стрелять в одном из них.И, возможно, это главная причина, почему вы должны запустить сопрограмму в ViewModel.Обратите внимание, что с viewModelScope нет необходимости cancel задание onCleared.

Простой пример:

viewModelScope.launch(Dispatchers.IO){
   val data = getDataSlowly()
   withContext(Dispatchers.MAIN){
    showData();
  }
} 

или без viewModelScope:

val job = Job () val coroutineScope = CoroutineContext (Dispatchers.MAIN + job)

fun fetchData(){
  coroutineScope.launch(){
 val data = getDataSlowly()
       withContext(Dispatchers.MAIN){
        showData();
      }
  }
}

// позже в модели представления:

override fun onCleared(){
  super.onCleared()
  job.cancel() //to prevent leaks
}

В противном случае ваш Service / Repository протечет.Другое примечание: NOT , чтобы использовать GlobalScope в этом случае.

...