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

Jetpack Compose中基于ViewModel的实时输入验证及状态回滚实现

解决方案:将输入验证逻辑移至ViewModel并处理临时输入与回退

1. 重构UiState,区分临时输入与有效状态

首先扩展原有UiState,新增临时输入文本、验证错误信息、原始有效值字段,满足实时输入追踪和取消回退的需求:

data class DepthUiState(
    // TextField绑定的用户实时输入文本
    val tempInputText: String = "",
    // 验证错误提示,null表示输入有效
    val validationError: String? = null,
    // 用户已确认保存的有效深度值
    val savedDepth: Int? = null,
    // 打开Dialog时记录的原始有效值,用于取消时恢复
    val originalSavedDepth: Int? = null
)

2. 提取验证逻辑到独立工具类(解耦UI与业务)

把验证逻辑从UI层移到独立类中,方便单元测试,不依赖任何UI组件:

object DepthValidator {
    fun validateDepthInput(input: String): String? {
        return when {
            input.isBlank() -> "深度不能为空"
            input.toIntOrNull() == null -> "请输入有效数字"
            input.toInt() !in 3..100 -> "深度必须在3-100之间"
            else -> null // 输入无错误
        }
    }

    // 辅助方法:将有效输入转为Int
    fun convertToValidDepth(input: String): Int? {
        return if (validateDepthInput(input) == null) input.toInt() else null
    }
}

3. 修改ViewModel,处理输入、验证与回退逻辑

ViewModel负责管理状态、执行验证、处理确认/取消操作,确保业务逻辑完全脱离UI层:

@HiltViewModel
class DepthViewModel @Inject constructor(
    private val depthRepository: DepthRepository,
) : ViewModel() {
    private val _uiState = MutableStateFlow(DepthUiState())
    val uiState = _uiState.asStateFlow()

    // 打开Dialog时初始化,记录当前有效值作为原始值
    fun onDialogOpened() {
        _uiState.update { currentState ->
            currentState.copy(
                originalSavedDepth = currentState.savedDepth,
                tempInputText = currentState.savedDepth?.toString() ?: ""
            )
        }
    }

    // 实时处理TextField输入变化并验证
    fun onTempInputChanged(input: String) {
        val error = DepthValidator.validateDepthInput(input)
        _uiState.update { it.copy(tempInputText = input, validationError = error) }
    }

    // 确认按钮点击:保存有效输入并提交到Repository
    fun onConfirmClicked() {
        val validDepth = DepthValidator.convertToValidDepth(_uiState.value.tempInputText)
        validDepth?.let {
            _uiState.update { currentState ->
                currentState.copy(
                    savedDepth = it,
                    originalSavedDepth = null
                )
            }
            // 提交到Repository
            viewModelScope.launch {
                depthRepository.saveDepth(it)
            }
        }
    }

    // 取消按钮点击:恢复原始有效值
    fun onCancelClicked() {
        _uiState.update { currentState ->
            currentState.copy(
                tempInputText = currentState.originalSavedDepth?.toString() ?: "",
                validationError = null,
                originalSavedDepth = null
            )
        }
    }
}

4. 更新Dialog Composable,与ViewModel联动

UI层仅负责显示状态和转发用户操作,所有逻辑交由ViewModel处理:

@Composable
fun DepthInputDialog(
    onDismissRequest: () -> Unit,
    viewModel: DepthViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsState()

    // 打开Dialog时初始化原始值
    LaunchedEffect(Unit) {
        viewModel.onDialogOpened()
    }

    Dialog(onDismissRequest = {
        viewModel.onCancelClicked()
        onDismissRequest()
    }) {
        Card(modifier = Modifier.padding(16.dp)) {
            Column(modifier = Modifier.padding(16.dp)) {
                TextField(
                    value = uiState.tempInputText,
                    onValueChange = viewModel::onTempInputChanged,
                    label = { Text("输入深度") },
                    isError = uiState.validationError != null,
                    supportingText = {
                        uiState.validationError?.let { Text(it, color = MaterialTheme.colorScheme.error) }
                    }
                )

                Spacer(modifier = Modifier.height(16.dp))

                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.End
                ) {
                    TextButton(onClick = {
                        viewModel.onCancelClicked()
                        onDismissRequest()
                    }) {
                        Text("取消")
                    }
                    Spacer(modifier = Modifier.width(8.dp))
                    Button(
                        onClick = {
                            viewModel.onConfirmClicked()
                            onDismissRequest()
                        },
                        enabled = uiState.validationError == null
                    ) {
                        Text("确认")
                    }
                }
            }
        }
    }
}

核心优势说明

  • 实时验证:用户每输入一个字符,ViewModel立即执行验证,UI层实时反馈错误状态
  • 取消回退:打开Dialog时记录原始有效值,取消操作会自动恢复该值,避免无效输入覆盖原有数据
  • 逻辑解耦:验证逻辑独立可测试,ViewModel专注状态管理,UI层仅做展示,符合MVVM架构规范
  • Repository前置验证:只有经过验证的有效值才会提交到Repository,确保数据安全性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 21:55:21