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

Jetpack Compose中LazyVerticalGrid结合MutableStateFlow删除项异常

问题:Jetpack Compose LazyVerticalGrid 删除项时UI更新错误

问题现象

使用LazyVerticalGrid展示技能列表,点击任意项执行删除操作后,ViewModel中的列表已正确更新,但UI始终移除最后一项而非选中项。

代码复现

ViewModel代码

@HiltViewModel
class PersonalViewModel @Inject constructor() : ViewModel() {
private val _skillsList = MutableStateFlow<List<String>>(emptyList())
val skillsList: StateFlow<List<String>> = _skillsList.asStateFlow()

fun addSkill(skill: String) {
    val lowercaseSkills = _skillsList.value.map { it.lowercase() }
    val skillExists = lowercaseSkills.contains(skill.lowercase())
    if (!skillExists) {
        val mutableSkillList = lowercaseSkills.toMutableList()
        mutableSkillList.add(skill)
        _skillsList.value = mutableSkillList
    }
}

fun removeSkill(skill: String) {
    val lowercaseSkills = _skillsList.value.map { it.lowercase() }
    val mutableSkillList = lowercaseSkills.toMutableList()
    mutableSkillList.remove(skill.lowercase())
    _skillsList.value = mutableSkillList
}
}

网格Composable代码

@Composable
fun SkillsGrid(personalViewModel: PersonalViewModel) {
    val skillsList by personalViewModel.skillsList.collectAsState()
    LazyVerticalGrid(
        // ... grid configuration
    ) {
        items(skillsList) { skill ->
            SkillsItem(skill = skill) {
                personalViewModel.removeSkill(skill)
            }
        }
    }
}

SkillsItem代码

@Composable
fun SkillsItem(
    skill: String,
    onItemRemoved: (String) -> Unit,
) {
    // ... UI for the skill item
    Button(onClick = { onItemRemoved(skill) }) {
        Text("Remove")
    }
}

问题原因

  1. Lazy列表默认key机制缺陷:LazyVerticalGrid的items方法默认使用列表索引作为项的唯一标识(key)。当列表元素被删除后,Compose会基于索引复用现有UI组件,导致视觉上最后一项被“移除”,而非实际删除的目标项。
  2. ViewModel数据逻辑不一致:addSkill将现有列表转为小写后存储,但添加的是原大小写的技能字符串,导致列表中元素大小写混乱;removeSkill再次将整个列表转小写后执行删除,进一步加剧了数据与UI展示的不匹配。

解决方案

1. 为LazyVerticalGrid指定唯一key

修改SkillsGrid中的items调用,明确指定每个项的唯一key,确保Compose能准确识别每个项的身份,避免重组时组件复用错误:

@Composable
fun SkillsGrid(personalViewModel: PersonalViewModel) {
    val skillsList by personalViewModel.skillsList.collectAsState()
    LazyVerticalGrid(
        // 示例网格配置,根据实际需求调整
        columns = GridCells.Fixed(2),
        contentPadding = PaddingValues(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = skillsList,
            // 用小写技能作为唯一key(因添加时忽略大小写,不会存在重复)
            key = { skill -> skill.lowercase() }
        ) { skill ->
            SkillsItem(skill = skill) {
                personalViewModel.removeSkill(skill)
            }
        }
    }
}

2. 统一ViewModel数据处理逻辑

优化addSkill和removeSkill方法,保持数据存储的一致性,同时简化逻辑:

@HiltViewModel
class PersonalViewModel @Inject constructor() : ViewModel() {
    private val _skillsList = MutableStateFlow<List<String>>(emptyList())
    val skillsList: StateFlow<List<String>> = _skillsList.asStateFlow()

    fun addSkill(skill: String) {
        val targetSkillLower = skill.lowercase()
        // 检查是否已存在(忽略大小写)
        val skillExists = _skillsList.value.any { it.lowercase() == targetSkillLower }
        if (!skillExists) {
            // 保留原技能字符串,直接追加到列表
            _skillsList.value = _skillsList.value + skill
        }
    }

    fun removeSkill(skill: String) {
        val targetSkillLower = skill.lowercase()
        // 过滤掉匹配的技能(忽略大小写)
        _skillsList.value = _skillsList.value.filter { it.lowercase() != targetSkillLower }
    }
}

说明

  • 指定唯一key是解决Lazy列表更新错误的核心,确保每个项的身份不依赖索引。
  • 统一数据逻辑后,ViewModel中的列表将保留技能原有的大小写,同时保证添加/删除操作的准确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:53:19