如何在 Fragment 中处理 Retrofit 中的超时

问题描述 投票:0回答:2

我想在发生超时时显示一条错误消息,但如果不传递

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:一些澄清

android kotlin retrofit kotlin-coroutines
2个回答
1
投票

我找到了一种方法,通过在

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
            }
        }
    }
}

0
投票

一个

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())
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.