Issue with Removing Items in LazyVerticalGrid Using MutableStateFlow in Jetpack Compose

134 Views Asked by At

I'm encountering a problem in my Jetpack Compose UI where I have a LazyVerticalGrid displaying a list of skills. Each skill is a clickable item that, when clicked, should remove the skill from the list. The list is managed by a MutableStateFlow in my ViewModel. However, when removing a skill, the LazyVerticalGrid does not seem to update correctly; the last item is always removed instead of the selected one.

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 for the grid:

@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:

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

I've confirmed via logging that PersonalViewModel.removeSkill is being called with the correct skill string, and the list in the ViewModel updates correctly. However, the UI displays the incorrect state after removal, always showing the last item removed regardless of which one I actually clicked.

What could be causing this issue, and how can I ensure the correct item is removed and the UI updates accordingly?

1

There are 1 best solutions below

0
On

I found a solution to the issue with the LazyVerticalGrid not updating correctly in my Jetpack Compose UI. The problem was related to how Jetpack Compose tracks and updates the items in the grid when there are changes in the data list. Specifically, Compose was not able to correctly identify which items in the list had changed when one was removed.

To fix this, I used a key in the items lambda of the LazyVerticalGrid. This key should uniquely identify each item in your list. Since each skill in my list is a unique string, I used the skill itself as the key. Here's the updated code for the LazyVerticalGrid:

LazyVerticalGrid(
    modifier = Modifier
        .fillMaxWidth()
        .heightIn(max = height * 2),
    columns = GridCells.Fixed(2),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp)
) {

        items(skillsList, key = { it }) { skill ->
            SkillsItem(skill = skill) { removedSkill ->
                personalViewModel.removeSkill(removedSkill)
            }
        }
    }

Adding a unique key for each item in the LazyVerticalGrid ensures that Jetpack Compose can accurately track which items are being removed and update the UI accordingly. After making this change, the grid now correctly reflects the removal of the selected item, instead of always removing the last item.