【问题标题】:How ViewModel could catch exception not thrown by the above layer?ViewModel 如何捕获上层未抛出的异常?
【发布时间】:2020-07-22 13:12:44
【问题描述】:

目前正在使用 Kotlin 开发 Android 应用程序,我决定学习 MVVM 模式。

我的情况是:

Fragment --> ViewModel --> Controller --> Repository

我的控制器仅用作当前会话的缓存。

目前能够联系我的 API,我想处理可能发生的潜在异常,例如“UnknownHostException”、“SocketTimeoutException”,并为每个异常显示不同的消息。

注意:我使用 retrofit 2.6.0协程 来完成这项工作

向API请求联系的步骤如下:

  • 片段订阅 viewModel 变量

  • viewModel 启动它的“fetchCommandList”方法,它启动一个带有 try/catch 的协程并要求控制器返回命令列表。

  • 如有必要,控制器会调用存储库。

  • 存储库尝试检索 try/catch 块中的数据

这就是我的问题所在。如果您没看错,我的控制器没有 try/catch,但是,如果在存储库中抛出异常,viewModel 会很好地捕获它并更改其 LiveData在片段中显示异常的具体消息。

我不明白异常如何能够“跳过”控制器层并仍然到达 viewModel。

下面是每一层的代码,从视图到模型:

关于错误 LiveData 的片段部分

mviewModel.mErrorStatus.observe(viewLifecycleOwner, Observer {
        mbinding.fragmentCommandListRecyclerview.visibility = if (it != null) View.GONE else View.VISIBLE
        mbinding.fragmentCommandListErrorTextView.visibility = if (it != null) View.VISIBLE else View.GONE
        mbinding.fragmentCommandListErrorTextView.text = it
    })

ViewModel 调用控制器

fun fetchCommandList(){
    //TODO : contact the controller method tha will firstly check is in list & if not result ask the repository to retrieve it
    viewModelScope.launch {
        mProgressBar.postValue(true)
        try {
            mCommandList.postValue(CommandControllerMock.getCommandsList())
            mCommandActive.postValue(CommandControllerMock.getCurrentCommand())
        }
        catch (e: UnknownHostException) {
            //this exception occurs when there is no internet connection or host is not available
            //so inform user that something went wrong
            mErrorStatus.postValue(e.message)
        } catch (e: SocketTimeoutException) {
            //this exception occurs when time out will happen
            //so inform user that something went wrong
            mErrorStatus.postValue(e.message)
        } catch (e: Exception) {
            //this is generic exception handling
            //so inform user that something went wrong
            mErrorStatus.postValue(e.message)
        }
        finally {
            mProgressBar.postValue(false)
        }

    }

}

viewModel 调用的控制器 getCommandsList(NO TRY/CATCH)

override suspend fun getCommandsList(): List<Command> {
        if(m_commandList.size <= 0){
            //Todo: replace by auth.userID
            //Todo : before return the result, check if a command is already active to set currentCommand
            val returnedList = m_commandRepository.getAllCommandByUser(USER_ID)
            returnedList?.let{
                m_commandList = it.toMutableList()
            }
            checkForActiveCommand()
        }
        return m_commandList
}

调用 API 端点的存储库方法

override suspend fun getAllCommandByUser(idUser: String): List<Command>? {
    try {

        val response = apiService.getAllCommandByUser(idUser)

        if (response.isSuccessful) {
            return response.body()
        } else {
            throw Exception("not 200 code")
        }
    } catch (e: Exception) {
        //this is generic exception handling
        //so inform user that something went wrong
        throw e
    }
}

例如,如果我在手机上禁用了 Internet,由于 UnknownHostException ,我会收到以下消息: “无法解析主机“MY_API_ADRESS”:没有与主机名关联的地址”

如果我在 OkHTTP 中设置了 1 秒的 readTimeout,由于 SocketTimeoutException,我会收到以下消息: “超时”

我只是不明白我忘记在控制器中抛出异常但它仍然被 ViewModel 捕获的部分。

【问题讨论】:

  • 在您的存储库中尝试捕获然后在具有throw e 的捕获中具有什么意义?
  • 我想传播异常,直到 viewModel 处理其中的 UI 显示。存储库只需要将例外提供给较低层
  • 那么如果 try catch 捕获异常并抛出相同的东西,它的作用是什么?那么你的 try catch 是多余的
  • 我想我不知道异常是如何工作的,对我来说,如果我不在我的存储库中这样做,异常就会丢失。
  • 嗯,你可以测试一下。编写一个函数来抛出一个像你这样的 try catch 的异常,然后一个没有,看看它们是不同的还是相同的

标签: android kotlin exception mvvm


【解决方案1】:

挂起的函数很自然地将运行时异常传播给顶级调用者,直到它遇到 try and catch 块或协程范围,然后由 exceptionHandler 处理或导致应用程序崩溃。

因此,在您的情况下,没有什么可疑的,并且不需要在存储库层进行异常重新抛出的 try&catch。

查看协程异常处理doc

【讨论】:

  • 所以,如果我理解得很好(对不起,我不是英语),异常将“自动”抛出,直到最高调用者(我的 ViewModel 这里)?所以我可以删除我的存储库中的 try catch 但让 ViewModel 中的那个?
  • 是的,并检查 Coroutines 文档中的异常处理,我在评论中添加了该文档
  • 感谢您的帮助!!
猜你喜欢
  • 1970-01-01
  • 2011-08-12
  • 1970-01-01
  • 2013-06-24
  • 1970-01-01
  • 2013-11-30
  • 1970-01-01
  • 2019-09-18
相关资源
最近更新 更多