在阅读了此问题How to deal with exception和此媒介Android Networking in 2019 — Retrofit with Kotlin’s Coroutines之后,我创建了一个解决方案,其中包含一个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
}
}
}
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决策逻辑位于该层中,例如,如果我们只想举杯,忽略异常类型,调用另一个函数,等等...
你怎么看?
答案 0 :(得分:1)
我认为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起)。
无论如何,我不能说这篇文章不好,但这并不是最好的解决方案。如果要遵循文章的指南,则可能要检查Transformations on the 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
将会泄漏。在这种情况下,另一个注意事项是不 to use the GlobalScope
。
答案 1 :(得分:0)
我建议处理存储库中的异常,并将单个响应以密封类对象的形式返回给viewmodel。将其保留在存储库中将使存储库成为事实的单一来源,而您的代码将更多 干净且可读。
答案 2 :(得分:0)
作为对@coroutineDispatcher 优秀答案的补充,我建议捕获异常并检查它是 HttpException、UnknownHostException 还是 SocketException,以便为用户提供有关异常的更多反馈。例如
➜ Projeto npm install webpack webpack-cli webpack-dev-server --save-dev
npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.
npm WARN deprecated fsevents@1.2.13: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.2.7 (node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
npm WARN projeto@1.0.0 No description
npm WARN projeto@1.0.0 No repository field.
+ webpack-dev-server@3.11.2
+ webpack-cli@4.7.0
+ webpack@5.38.1
updated 3 packages and audited 499 packages in 35.305s
1 package is looking for funding
run `npm fund` for details
found 1 moderate severity vulnerability
run `npm audit fix` to fix them, or `npm audit` for details
➜ Projeto material-ui
zsh: command not found: material-ui
➜ Projeto npm install @ material-ui / core
npm WARN deprecated material-ui@0.20.2: You can now upgrade to @material-ui/core
npm ERR! code EINVALIDTAGNAME
npm ERR! Invalid tag name "@": Tags may not have any characters that encodeURIComponent encodes.
npm ERR! A complete log of this run can be found in:
npm ERR! /home/peter/.npm/_logs/2021-06-02T04_35_32_632Z-debug.log
以上所有代码都在if (throwable is SocketException) {
//very bad internet
return
}
if (throwable is HttpException) {
// parse error body message from
//throwable.response().errorBody()
return
}
if (throwable is UnknownHostException) {
// probably no internet or your base url is wrong
return
}
ShowSomethingWentWrong()
内