You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 17:17:49