如何在Jetpack Compose中使用ViewModel优雅处理多种服务器错误
嗨,作为Jetpack Compose新手,从Fragment的LiveData模式切换过来确实需要调整下思路,我来帮你梳理几种优雅的错误处理方案:
方案1:适配你现有LiveData的快速迁移
如果你暂时不想改动ViewModel里的LiveData实现,可以在Composable中用collectAsStateWithLifecycle来观察状态变化,再结合LaunchedEffect处理一次性的错误提示:
首先在ViewModel里加一个重置错误状态的方法,避免重复显示错误:
fun clearError() { _errorBase.postValue(null) }
然后在你的登录页面Composable中:
@Composable fun LoginScreen(viewModel: LoginViewModel = viewModel()) { // 收集LiveData状态,自动感知生命周期 val errorState by viewModel.errorBase.collectAsStateWithLifecycle() // 用于显示Snackbar(比Toast更贴合Compose风格) val snackbarHostState = remember { SnackbarHostState() } Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding -> Column(modifier = Modifier.padding(padding)) { // 你的登录表单、按钮等UI代码 Button(onClick = { viewModel.login("账号", "密码") }) { Text("登录") } } } // 当错误状态不为空时,触发提示 errorState?.let { errorHelper -> LaunchedEffect(key1 = errorHelper) { val errorMsg = viewModel.getErrorMessage(errorHelper) snackbarHostState.showSnackbar(errorMsg) // 显示后重置错误状态,防止旋转屏幕后重复触发 viewModel.clearError() } } } // 把原来的onError移到ViewModel里,保持逻辑内聚 fun LoginViewModel.getErrorMessage(errorHelper: ErrorHelper): String { return when (errorHelper.errorStatus) { ERROR_STATUS.ERR -> "服务器开小差了,请稍后重试" ERROR_STATUS.NETWORK -> "网络连接失败,请检查你的网络" ERROR_STATUS.INVALIDCRED -> "用户名或密码不正确" else -> "未知错误,请联系客服" } }
方案2:改用Flow(更推荐的Compose适配方案)
LiveData是为传统View体系设计的,Flow更适配Compose的状态驱动理念。可以把ViewModel里的LiveData换成MutableSharedFlow(适合一次性事件,比如错误提示):
// ViewModel中修改 private val _errorBase = MutableSharedFlow<ErrorHelper>() val errorBase: SharedFlow<ErrorHelper> = _errorBase fun login(a: String, b: String) { viewModelScope.launch { val result = repository.login(a, b) when(result) { is Result.Error -> _errorBase.emit(result.error as ErrorHelper) // 处理成功逻辑... } } }
然后在Composable中用LaunchedEffect直接收集Flow:
@Composable fun LoginScreen(viewModel: LoginViewModel = viewModel()) { val snackbarHostState = remember { SnackbarHostState() } Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { // 登录UI... } // 收集错误流,触发提示 LaunchedEffect(key1 = viewModel.errorBase) { viewModel.errorBase.collect { errorHelper -> val errorMsg = viewModel.getErrorMessage(errorHelper) snackbarHostState.showSnackbar(errorMsg) } } }
方案3:统一UI状态管理(最优雅的长期方案)
把登录的所有状态(加载、成功、错误)用密封类统一管理,让界面逻辑更清晰:
// 定义登录UI状态密封类 sealed class LoginUiState { object Idle : LoginUiState() object Loading : LoginUiState() object Success : LoginUiState() data class Error(val errorHelper: ErrorHelper) : LoginUiState() } // ViewModel中维护UI状态 private val _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Idle) val uiState: StateFlow<LoginUiState> = _uiState fun login(a: String, b: String) { viewModelScope.launch { _uiState.value = LoginUiState.Loading val result = repository.login(a, b) _uiState.value = when(result) { is Result.Success -> LoginUiState.Success is Result.Error -> LoginUiState.Error(result.error as ErrorHelper) } } }
然后在Composable中根据UI状态渲染界面:
@Composable fun LoginScreen(viewModel: LoginViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val snackbarHostState = remember { SnackbarHostState() } Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding -> Column(modifier = Modifier.padding(padding)) { when(uiState) { LoginUiState.Idle -> { // 显示登录表单 Button(onClick = { viewModel.login("账号", "密码") }) { Text("登录") } } LoginUiState.Loading -> { // 显示加载动画 CircularProgressIndicator() } LoginUiState.Success -> { // 登录成功后的界面,比如跳转到主页 Text("登录成功!") } is LoginUiState.Error -> { // 错误状态下,依然显示表单,同时触发提示 Button(onClick = { viewModel.login("账号", "密码") }) { Text("登录") } LaunchedEffect(key1 = uiState) { val errorMsg = viewModel.getErrorMessage(uiState.errorHelper) snackbarHostState.showSnackbar(errorMsg) } } } } } }
这种方式把所有界面状态集中管理,完全符合Compose"状态驱动UI"的核心思想,也更容易维护和扩展。
内容的提问来源于stack exchange,提问作者Suraya
相关产品推荐
相关产品推荐

