I am trying to achieve something like the image at...
# compose
e
I am trying to achieve something like the image attached with a Stack, where each element I add is placed at the bottom of the stack, with each element having a random color. The issue is that each time I add a new element the Composables lose the state even when I remember it using the id as a key. The final behaviour I want to achieve is much more complex than this and involves dragging elements, but it breaks for the same reason when trying to remember the position of the different draggable elements. I built a minimum example that reproduces this issue, is there a solution to this kind of scenarios?
Copy code
@Composable
fun Body() {
    Stack(Modifier.fillMaxSize()) {
        var items by remember { mutableStateOf(listOf(0)) }
        items.reversed().forEach {
            Item(
                it,
                modifier = Modifier
                    .clickable(onClick = {
                        items += items.last() + 1
                    })
                    .gravity(Alignment.Center)
                    .matchParentSize()
                    .padding((100 - (it * 10)).dp)
            )
        }
    }
}

@Composable
fun Item(num: Int, modifier: Modifier = Modifier) {
    val color = remember(num) { Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()) }
    Box(modifier = modifier, backgroundColor = color, gravity = Alignment.Center) {
        Text(text = "$num", style = MaterialTheme.typography.h1)
    }
}
v
mutableStateOf(listOf(0))
You could instead use
mutableStateListOf
a
when you mention using an id as the key do you mean
Copy code
key(it) {
  Item(...)
}
? If not, try wrapping a call to
key
with an appropriate key parameter around the call to
Item
e
@Vinay Gaba Thank you, it is a refactor it wasn't a list previously but id didn't know this other method. @Adam Powell I a refering to the
val color = remember(num) { Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()) }
inside the Item
wrapping the Item in the
key(it) { /**/ }
seems to fix the problem. Is there an explanation of how and when to use it? The documentation of the method is not clear in that regard and I would never expect it to fix this particular issue based on what it says
a
@Leland Richardson [G] could clarify for sure, but I think we have some more planned work with regard to some kinds of loops that might help this be a bit more automatic. The
key()
composable should be used whenever some elements might change or reorder with respect to one another; it helps compose keep state continuity. If you just have several composable function calls in sequence it's not an issue, but occasionally it's necessary to disambiguate.
👍 1
c
In general,
key
should be to wrap the content generated for an element of a collection. Using
LazyColorFor
or similar API, this happens implicitly. The parameter to
key
should be the element being represented. In your case, where
Item
is used to render an element of
items
the element from
items
should be the parameter of
key
. We use the
key
parameter(s) to track movement of the content generated with the data the content is for so we can keep the state and nodes generated for a particular piece of data, such as
color
, with the element it is generated for. If we don't have a
key
we re-use the state and nodes in the order they were created. This means that if the order of the array changes, value of the
Text
inside item will change but the the
color
will not appear to move with those values. The
key
parameters should be, but are not required to be, unique in the collection. If they are not unique the states are reused in the order they were generated. That is, if
items
contains two
10
values, the first
10
will always get the first color and the second will get the second color. However, if the first
10
is removed, the second
10
will reuse the first `10`s state and the color will appear to jump down the list which might be unexpected so it is highly recommended to use unique keys.