Kotlin/KMM登录验证:如何在两个协程作用域中捕获异常?
如何在协程中捕获登录凭据无效的异常
核心思路
仓库层抛出的异常会向上传递到ViewModel的协程作用域中,我们可以在ViewModel的协程内通过try-catch捕获异常,再通过状态通知UI层处理;UI层则通过观察ViewModel的状态变量来更新界面(比如隐藏加载、显示错误提示)。
1. 修改ViewModel,捕获异常并更新状态
首先在ViewModel中添加错误状态变量,然后在协程内用try-catch包裹仓库的登录调用,分别处理成功和失败场景:
// 定义状态变量,供UI层观察 private val _loginStatus = MutableStateFlow(false) val loginStatus: StateFlow<Boolean> = _loginStatus private val _loginError = MutableStateFlow<String?>(null) val loginError: StateFlow<String?> = _loginError fun doLogin(userName: String, password: String) { viewModelScope.launch(Dispatchers.IO) { try { // 调用仓库登录接口,成功则获取User对象 val user = repo.login(userName, password) // 切换到主线程更新成功状态 withContext(Dispatchers.Main) { _loginStatus.value = true _loginError.value = null // 清空错误信息 } } catch (e: Exception) { // 捕获登录异常(包括凭据无效) withContext(Dispatchers.Main) { _loginStatus.value = false // 根据异常类型设置错误提示,可根据实际情况调整判断逻辑 _loginError.value = if (e.message?.contains("invalid credentials") == true) { "用户名或密码错误" } else { "登录失败,请稍后重试" } } } } }
2. UI层观察状态,处理结果
在登录页面的Composable中,收集ViewModel的状态变量,根据状态更新加载状态和错误提示:
@Composable fun LoginScreen(loginViewModel: LoginViewModel) { val userName = remember { mutableStateOf("") } val password = remember { mutableStateOf("") } var showProgress by remember { mutableStateOf(false) } // 收集ViewModel的状态 val loginError by loginViewModel.loginError.collectAsState() val loginStatus by loginViewModel.loginStatus.collectAsState() // 监听状态变化,更新加载状态 LaunchedEffect(loginStatus, loginError) { if (loginStatus || loginError != null) { showProgress = false } } Column(Modifier.padding(16.dp)) { // 用户名、密码输入框实现... // 显示错误提示 loginError?.let { Text( text = it, color = Color.Red, modifier = Modifier.padding(vertical = 8.dp) ) } // 登录按钮 Button( onClick = { showProgress = true loginViewModel.doLogin(userName.value, password.value) }, enabled = !showProgress ) { if (showProgress) { CircularProgressIndicator(modifier = Modifier.size(20.dp)) } else { Text(text = "登录") } } } }
进阶优化:自定义异常(可选)
如果需要更精准地区分"凭据无效"和其他异常(比如网络错误),可以在仓库层定义自定义异常:
// 自定义凭据无效异常 class InvalidCredentialsException(message: String = "用户名或密码错误") : Exception(message) // 仓库层代码修改 suspend fun login(email: String, password: String): User { return try { appService.login(Credentials.emailPassword(email, password)) } catch (e: Exception) { // 根据底层服务的异常信息判断,抛出自定义异常 if (e.message?.contains("invalid") == true) { throw InvalidCredentialsException() } else { throw e // 其他异常原样抛出 } } }
然后ViewModel中可以精准捕获:
catch (e: InvalidCredentialsException) { withContext(Dispatchers.Main) { _loginError.value = e.message } } catch (e: Exception) { withContext(Dispatchers.Main) { _loginError.value = "网络异常,请检查网络连接" } }
内容的提问来源于stack exchange,提问作者user14723892
相关产品推荐
相关产品推荐

