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

屏幕旋转时SnackBar重复弹出问题求助(RegistrationUiState.Error)

问题分析

你的问题根源在于:屏幕旋转会触发Composable重组,而RegistrationUiState.Error是一个持久化的状态(ViewModel在配置变更后仍保留该状态),导致每次重组时LaunchedEffect都会重新执行,从而重复弹出SnackBar。

解决方案

方案一:使用ViewModel一次性事件(推荐)

这是Jetpack Compose处理一次性UI事件(如弹出SnackBar、导航)的标准做法,避免状态和事件混淆。

步骤1:在ViewModel中添加事件流

// 定义一次性事件密封类
sealed class RegistrationEvent {
    object ShowPasswordUpdateError : RegistrationEvent()
    object ShowPasswordUpdateSuccess : RegistrationEvent()
}

class UpdatePasswordViewModel : ViewModel() {
    private val _updatePasswordUiState = MutableStateFlow<RegistrationUiState>(RegistrationUiState.Initial)
    val updatePasswordUiState = _updatePasswordUiState.asStateFlow()

    // 用于发送一次性事件的SharedFlow
    private val _events = MutableSharedFlow<RegistrationEvent>()
    val events = _events.asSharedFlow()

    // 密码更新失败时调用
    private fun onUpdateFailed() {
        viewModelScope.launch {
            _events.emit(RegistrationEvent.ShowPasswordUpdateError)
        }
        // 重置状态为Initial,让表单可再次操作
        _updatePasswordUiState.value = RegistrationUiState.Initial
    }

    // 密码更新成功时调用
    private fun onUpdateSuccess() {
        viewModelScope.launch {
            _events.emit(RegistrationEvent.ShowPasswordUpdateSuccess)
        }
        _updatePasswordUiState.value = RegistrationUiState.Success
    }
}

步骤2:在Composable中收集事件并处理

@Composable
fun UpdatePasswordScreen(
    viewModel: UpdatePasswordViewModel = hiltViewModel(),
    navigateBack: () -> Unit
) {
    val uiState by viewModel.updatePasswordUiState.collectAsStateWithLifecycle()
    val context = LocalContext.current
    val snackbarHostState = remember { SnackbarHostState() }

    // 收集一次性事件
    LaunchedEffect(viewModel) {
        viewModel.events.collect { event ->
            when (event) {
                RegistrationEvent.ShowPasswordUpdateError -> {
                    snackbarHostState.showSnackbar(
                        context.getString(R.string.could_not_update_password_error),
                        context.getString(R.string.dialog_action_ok),
                        duration = SnackbarDuration.Short
                    )
                }
                RegistrationEvent.ShowPasswordUpdateSuccess -> {
                    snackbarHostState.showSnackbar(
                        context.getString(R.string.password_changed_successfully),
                        context.getString(R.string.dialog_action_ok),
                        duration = SnackbarDuration.Short
                    )
                    navigateBack()
                }
            }
        }
    }

    Scaffold(
        snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
        topBar = {
            MifosTopBar(
                title = { Text(text = stringResource(id = R.string.change_password)) },
                navigateBack = navigateBack
            )
        }
    ) { padding ->
        Box(modifier = Modifier.padding(padding)) {
            UpdatePasswordContent()

            when (uiState) {
                is RegistrationUiState.Loading -> {
                    MifosProgressIndicator(
                        modifier = Modifier
                            .fillMaxSize()
                            .background(MaterialTheme.colorScheme.background.copy(0.8f))
                    )
                }
                else -> Unit // Error和Success通过事件处理
            }
        }
    }
}

方案二:仅修改Composable(无需改动ViewModel)

如果暂时不想修改ViewModel结构,可以通过在Composable中跟踪已显示的错误来避免重复弹出:

@Composable
fun UpdatePasswordScreen(
    uiState: RegistrationUiState,
    navigateBack: () -> Unit
) {
    val context = LocalContext.current
    val snackbarHostState = remember { SnackbarHostState() }
    // 保存最后一次显示过SnackBar的错误状态
    val lastShownError = rememberSaveable { mutableStateOf<RegistrationUiState.Error?>(null) }

    Scaffold(
        snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
        topBar = {
            MifosTopBar(
                title = { Text(text = stringResource(id = R.string.change_password)) },
                navigateBack = navigateBack
            )
        }
    ) { padding ->
        Box(modifier = Modifier.padding(padding)) {
            UpdatePasswordContent()

            when (uiState) {
                is RegistrationUiState.Loading -> {
                    MifosProgressIndicator(
                        modifier = Modifier
                            .fillMaxSize()
                            .background(MaterialTheme.colorScheme.background.copy(0.8f))
                    )
                }

                is RegistrationUiState.Error -> {
                    // 仅当是新错误时才显示SnackBar
                    if (lastShownError.value != uiState) {
                        LaunchedEffect(snackbarHostState, uiState) {
                            snackbarHostState.showSnackbar(
                                context.getString(R.string.could_not_update_password_error),
                                context.getString(R.string.dialog_action_ok),
                                duration = SnackbarDuration.Short
                            )
                            lastShownError.value = uiState
                        }
                    }
                }

                is RegistrationUiState.Initial -> {
                    // 回到初始状态时重置跟踪的错误
                    lastShownError.value = null
                }

                is RegistrationUiState.Success -> {
                    LaunchedEffect(snackbarHostState) {
                        snackbarHostState.showSnackbar(
                            context.getString(R.string.password_changed_successfully),
                            context.getString(R.string.dialog_action_ok),
                            duration = SnackbarDuration.Short
                        )
                    }
                    navigateBack.invoke()
                }
            }
        }
    }
}

注意事项

  • 方案二要求RegistrationUiState.Error是data class,这样才能通过对象相等性判断是否为新错误。
  • 方案一更符合MVVM架构设计原则,将状态和事件分离,避免持久化状态触发重复操作。

内容的提问来源于stack exchange,提问作者Pronay Sarker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:07:03