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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 10:12:06