我想在发生超时时显示一条错误消息,但如果不传递
loginProgressBar
和 loginTimeoutErrorTextview
或其他与 UI 相关的变量,就找不到如何去做。基本上保留 Fragment
. 中的 UI 更改
按登录时我在
loginFragment
中做什么
try {
loginViewModel.login(
binding.emailEditText.text.toString(),
binding.passwordEditText.text.toString()
)
} catch (e: IOException) { // THIS doesn't work
binding.loginProgressBar.visibility = View.GONE
binding.loginTimeoutErrorTextview.visibility = View.VISIBLE
}
根据我的发现,超时会抛出一个
IOException
所以这里的一切都是正确的
loginViewModel.login
方法:
viewModelScope.launch {
val securedLoginRequest = encodedRequest(username, password)
Log.i("API Login", "Sent data: $securedLoginRequest")
try {
Log.i("API Login", "login started")
// _response is a LiveData<String>
_response.value =
ApiServiceObject.retrofitService.postLogin(securedLoginRequest)
Log.i("API Login", "Login successful, token = ${_response.value}")
} catch (e: IOException) {
throw e // THIS should theoretically get catched by the block above right?
} catch (e: Exception) {
Log.w("API Login", e.toString())
}
}
问题是 Block 2 中抛出的异常没有在 Block 1 中捕获
我找到了一个解决方法,只需将
loginProgressBar
和 loginTimeoutErrorTextview
传递给 loginViewModel.login
但这可以吗?没有更好的方法吗?
UPD:一些澄清
我找到了一种方法,通过在
StateFlow
中使用 LiveData
而不是 ViewModel
如下:
private val _loginState = MutableStateFlow<LoginState>(LoginState.Empty)
val loginState = _loginState.asStateFlow()
sealed class LoginState {
object Empty : LoginState()
object Loading : LoginState()
data class Success(val result: String) : LoginState()
data class Error(val error: Throwable) : LoginState()
}
然后在
Fragment
中这样回应它:
lifecycleScope.launch{
repeatOnLifecycle(Lifecycle.State.STARTED){
loginViewModel.loginState.collect { state ->
binding.loginProgressBar.isGone = state !is LoginState.Loading
when (state) {
is LoginState.Error -> { doSomething() }
is LoginState.Success -> { doSomething() }
else -> Unit
}
}
}
}
一个
viewModelScope
是一个CoroutineScope
。而 Coroutine 就像一个 Thread(有关 Coroutine 的更多详细信息,您可以在 here 阅读它)。
所以你不能有这样的外部 try-catch:
try {
viewModelScope.launch {
...
throw IOException("Some IO Exception")
}
} catch (e: IOException) {
// It will never pass here and instead the program should crash above
...
}
在 Coroutine 中处理可能的异常的正确方法是 在其中包含 try-catch:
viewModelScope.launch {
val securedLoginRequest = encodedRequest(username, password)
Log.i("API Login", "Sent data: $securedLoginRequest")
try {
Log.i("API Login", "login started")
// _response is a LiveData<String>
_response.value =
ApiServiceObject.retrofitService.postLogin(securedLoginRequest)
Log.i("API Login", "Login successful, token = ${_response.value}")
} catch (e: IOException) {
// You should either have a Callback to return this Exception outside this Coroutine,
// or you should handle the UI update here
// If you throw Exception here, your program should crash
} catch (e: Exception) {
Log.w("API Login", e.toString())
}
}