Removing item from the list causes wrong display in LazyColumn
Asked Answered
W

1

7

So here's an odd one, I think. I'm showing a list of Textfields in a LazyColoumn. The user can remove each of the textfields, but when doing so, it copies the value from REPLACE textfield.

Before remove

After remove

What's happening:

I have added 3 people: Person 1, Person 2, Person 3

I click remove Person 2.

Person 3 is now at Person 2's position (See the name), but copied Person 2' VALUE.

I manage the state like this:

private val peopleStateFlow = MutableStateFlow<List<Person>>()

I load the column like this:

val peopleState = viewModel.peopleState.collectAsState()
LazyColumn {
    val peopleStateSnap = peopleState.value
    items(peopleStateSnap.size) { index ->
    val person = peopleStateSnap[index]
    ParticipantView(
        person = person,
        sharedOwed = sharedOwed.value,
        onChangeListener = {
            viewModel.updateOwed(person, it)
        },
        onRemoveClicked = {
            viewModel.removePerson(person)
        })
    }
}

And I remove the person like this:

fun removePerson(person: Person) {
        val indexOf = peopleState.value.indexOf(person)
        val updateList = peopleState.value.toMutableList()
        updateList.removeAt(indexOf)
        peopleStateFlow.value = updateList
    }

I even tried logging this list before and after the removal

21:22:05.468  I  qqq oldList=[1.0, 2.0, 0.0]
21:22:05.468  I  qqq removed = 2.0
21:22:05.468  I  qqq updateList=[1.0, 0.0]

And it is seemingly being removed correctly, so the problem lies 100% with recompose, or how Compose manage a LazyColumn's or Textfield's state.

Whitehead answered 9/11, 2022 at 20:46 Comment(0)
A
7

I can't compile your code, though I suspect its similar to this, your LazyColumn is only identifying the position of the items based on its index position and not something unique from its supplied data structure.

And based on the official docs

By default, each item's state is keyed against the position of the item in the list or grid. However, this can cause issues if the data set changes, since items which change position effectively lose any remembered state. If you imagine the scenario of LazyRow within a LazyColumn, if the row changes item position, the user would then lose their scroll position within the row.

So I'd assume you have something unique in your Person properties that you can supply as a key to your LazyColumn's item.

items(peopleStateSnap.size, key = { < something unique to person here > } ) { index ->
   ...
}

Update:

There seems to be another way without using unique/stable keys for a Lazy item when your data doesn't have one and if circumstances prevent you from modifying its structure.

You can wrap your item's composable inside a movableContentOf{...} so their states are being tracked on Lazy changes, something like this

LazyColumn {
    
    items(peopleStateSnap.size) { index ->
        val person = peopleStateSnap[index]

        val movableContent = movableContentOf {
            ParticipantView(
                person = person,
                sharedOwed = sharedOwed.value,
                onChangeListener = {
                    viewModel.updateOwed(person, it)
                },
                onRemoveClicked = {
                    viewModel.removePerson(person)
                })
        }

        movableContent()
    }
}
Acreinch answered 10/11, 2022 at 1:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.