Room词汇重复检测:实现添加按钮禁用功能求助
问题描述
我正在开发一款词汇应用,希望实现如下功能:向Room数据库添加单词时,若该单词已存在,则禁用添加按钮。我已在ViewModel中创建了挂起函数exitItem(),用于检测输入单词是否存在(存在则返回true),恳请各位提供解决方案,感谢。
单词添加界面

ViewModel代码
class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewModel() { var itemUiState by mutableStateOf(ItemUiState()) private set fun updateUiState(itemDetails: ItemDetails) { itemUiState = ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) } suspend fun exitItem(): Boolean { return itemsRepository.isExistItem(word = itemUiState.itemDetails.word) } suspend fun saveItem() { if (validateInput()&&!exitItem()) { itemsRepository.insertItem(itemUiState.itemDetails.toItem()) } } private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { return with(uiState) { meaning.isNotBlank() && word.isNotBlank() } } } data class ItemUiState( val itemDetails: ItemDetails = ItemDetails(), val isEntryValid: Boolean = false, val isExit: Boolean = false ) data class ItemDetails( val id: Int = 0, val word:String="", val meaning: String = "" ) fun ItemDetails.toItem(): Item = Item( id = id, word=word, meaning = meaning ) fun Item.toItemUiState(isEntryValid: Boolean = false,isExit: Boolean=false): ItemUiState = ItemUiState( itemDetails = this.toItemDetails(), isEntryValid = isEntryValid, isExit = isExit ) fun Item.toItemDetails(): ItemDetails = ItemDetails( id = id, meaning=meaning, word = word )
代码说明
updateUiState():UI数据变更时更新ViewModel中的数据validateInput():检查Word和Meaning字段是否已填写saveItem():向Room写入数据
UI代码
@Composable fun ItemEntryScreen( navigateBack: () -> Unit, onNavigateUp: () -> Unit, canNavigateBack: Boolean = true, viewModel: ItemEntryViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { val coroutineScope = rememberCoroutineScope() Scaffold( topBar = { WordTopAppBar( title = stringResource(ItemEntryDestination.titleRes), canNavigateBack = canNavigateBack, navigateUp = onNavigateUp ) } ) { innerPadding -> ItemEntryBody( itemUiState = viewModel.itemUiState, onItemValueChange = viewModel::updateUiState, onSaveClick = { coroutineScope.launch { viewModel.saveItem() navigateBack() } }, modifier = Modifier .padding(innerPadding) .verticalScroll(rememberScrollState()) .fillMaxWidth() ) } } @Composable fun ItemEntryBody( itemUiState: ItemUiState, onItemValueChange: (ItemDetails) -> Unit, onSaveClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_large)) ) { ItemInputForm( itemDetails = itemUiState.itemDetails, onValueChange = onItemValueChange, modifier = Modifier.fillMaxWidth() ) Button( onClick = onSaveClick, enabled = itemUiState.isEntryValid, shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.save_action)) } } } @Composable fun ItemInputForm( itemDetails: ItemDetails, modifier: Modifier = Modifier, onValueChange: (ItemDetails) -> Unit = {}, enabled: Boolean = true ) { Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { OutlinedTextField( value = itemDetails.word, onValueChange = { onValueChange(itemDetails.copy(word = it)) }, label = { Text("WORD") }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), modifier = Modifier.fillMaxWidth(), enabled = enabled, singleLine = true ) OutlinedTextField( value = itemDetails.meaning, onValueChange = { onValueChange(itemDetails.copy(meaning = it)) }, label = { Text(stringResource(R.string.item_name_req)) }, colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), modifier = Modifier.fillMaxWidth(), enabled = enabled, singleLine = true ) if (enabled) { Text( text = stringResource(R.string.required_fields), modifier = Modifier.padding(start = dimensionResource(id = R.dimen.padding_medium)) ) } } }
解决方案
要实现单词存在时禁用添加按钮的功能,需要在用户输入单词时实时检查数据库,并更新UI状态控制按钮可用性,具体步骤如下:
1. 优化ViewModel,实时更新单词存在状态
修改updateUiState函数,在更新UI状态的同时启动协程检查单词是否存在,同步更新ItemUiState中的isExit字段:
fun updateUiState(itemDetails: ItemDetails) { val isEntryValid = validateInput(itemDetails) itemUiState = ItemUiState(itemDetails = itemDetails, isEntryValid = isEntryValid) // 仅当输入有效且单词不为空时,触发数据库检查 if (isEntryValid && itemDetails.word.isNotBlank()) { viewModelScope.launch { val exists = exitItem() itemUiState = itemUiState.copy(isExit = exists) } } else { // 输入无效时重置存在状态 itemUiState = itemUiState.copy(isExit = false) } }
2. 修改按钮启用条件
在ItemEntryBody中,按钮的enabled属性需要同时满足输入有效和单词不存在两个条件:
Button( onClick = onSaveClick, enabled = itemUiState.isEntryValid && !itemUiState.isExit, shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.save_action)) }
3. 可选:添加重复提示(提升用户体验)
在ItemEntryBody的按钮上方添加提示文本,当单词存在时显示:
// 放在ItemInputForm和Button之间 if (itemUiState.isExit) { Text( text = "该单词已存在", color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(start = dimensionResource(id = R.dimen.padding_medium)) ) }
关键说明
- 使用
viewModelScope绑定协程生命周期,避免内存泄漏 - 仅在输入有效时触发数据库查询,减少不必要的性能消耗
- 按钮启用条件同时校验输入有效性和单词唯一性,双重保障数据正确性
内容的提问来源于stack exchange,提问作者Trần Thanh Vinh
相关产品推荐
相关产品推荐

