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
相关产品推荐
相关产品推荐

