I have a component with some mutable state list. I pass an item of that, and a callback to delete the item, to another component.
@Composable
fun MyApp() {
val myItems = mutableStateListOf("1", "2", "3")
LazyColumn {
items(myItems) { item ->
MyComponent(item) { toDel -> myItems.remove(toDel) }
}
}
}
The component calls the delete
callback in a clickable
Modifier.
@Composable
fun MyComponent(item: String, delete: (String) -> Unit = {}) {
Column {
Box(
Modifier
.size(200.dp)
.background(MaterialTheme.colors.primary)
.clickable { delete(item) }
) {
Text(item, fontSize = 40.sp)
}
}
}
This works fine. But when I change the clickable
for my own Modifier with pointerInput()
then there's a problem.
fun Modifier.myClickable(delete: () -> Unit) =
pointerInput(Unit) {
awaitPointerEventScope { awaitFirstDown() }
delete()
}
@Composable
fun MyComponent(item: String, delete: (String) -> Unit = {}) {
Column {
Box(
Modifier
.size(200.dp)
.background(MaterialTheme.colors.primary)
.myClickable { delete(item) } // NEW
) {
Text(item, fontSize = 40.sp)
}
}
}
If I click on the first item, it removes it. Next, if I click on the newest top item, the old callback for the now deleted first item is called, despite the fact that the old component has been deleted.
I have no idea why this happens. But I can fix it. I use key()
:
@Composable
fun MyApp() {
val myItems = mutableStateListOf("1", "2", "3")
LazyColumn {
items(myItems) { item ->
key(item) { // NEW
MyComponent(item) { toDel -> myItems.remove(toDel) }
}
}
}
}
So why do I need key()
when I use my own modifier? This is also the case in this code from jetpack, and I don't know why.
As the accepted answer says, Compose won't recalculate my custom Modifier because pointerEvent()
doesn't have a unique key.
fun Modifier.myClickable(key:Any? = null, delete: () -> Unit) =
pointerInput(key) {
awaitPointerEventScope { awaitFirstDown() }
delete()
}
and
Box(
Modifier
.size(200.dp)
.background(MaterialTheme.colors.primary)
.myClickable(key = item) { delete(item) } // NEW
) {
Text(item, fontSize = 40.sp)
}
fixes it and I don't need to use key()
in the outer component. I'm still unsure why I don't need to send a unique key to clickable {}
, however.
Compose is trying to cache as many work as it can by localizing scopes with keys: when they haven't changes since last run - we're using cached value, otherwise we need to recalculate it.
By setting key
for lazy item you're defining a scope for all remember
calculations inside, and many of system functions are implemented using remember
so it changes much. Item index is the default key in lazy item
So after you're removing first item
, first lazy item gets reused with same context as before
And now we're coming to your myClickable
. You're passing Unit
as a key
into pointerInput
(It has a remember
inside too). By doing this you're saying to recomposer: never recalculate this value until context changes. And the context of first lazy item hasn't changed, e.g. key
is still same index, that's why lambda with removed item
remains cached inside that function
When you're specifying lazy item key
equal to item
, you're changing context of all lazy items too and so pointerInput
gets recalculated. If you pass your item
instead of Unit
you'll have the same effect
So you need to use key
when you need to make use your calculations are not gonna be cached between lazy items in a bad way
Check out more about lazy column keys in the documentation