https://kotlinlang.org logo
#compose
Title
# compose
a

abu naser

10/18/2022, 5:18 AM
hello guys . the problem is , in my project i need to have press and long press event in lazy item.. after the item change the lazy item’s pointer input keeping the old state or something . but if i use only clickable its fine . here is the video of the problem and sample code will be in comment .. TIA
👀 1
1
Copy code
@Composable
fun ProblemOfLazyClickable() {
    var counter by remember { mutableStateOf(0) }
    val dummyItemList = remember {
        mutableStateListOf("dummy init")
    }
    Column(modifier = Modifier.fillMaxSize()) {
        Button(onClick = { dummyItemList.add("dummy item $counter") ; counter++} ) {
            Text(text = "Add Dummy")
        }
        Button(onClick = { dummyItemList.remove(dummyItemList.random()) }) {
            Text(text = "Remove random Dummy")
        }
        LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 100.dp), content = {
            items(dummyItemList){ dummy ->
                DummyCard(item = dummy, onClick ={
                    Log.d("dummyEvent","Clicked $it")
                }, onLongClick = {
                    Log.d("dummyEvent","Long Clicked $it")
                } )
            }
        })
    }

}

@Composable
fun DummyCard(item:String,onClick:(String)->Unit ,onLongClick:(String)->Unit) {
    Card(modifier = Modifier
        .pointerInput(Unit){
            detectTapGestures(
                onPress = { onClick(item) },
                onLongPress = { onLongClick(item) }
            )
        }
        .padding(4.dp)
        .sizeIn(100.dp, 200.dp, 100.dp, 200.dp)
        //clickable works fine
//        .clickable { onClick(item) }
    , backgroundColor = Color.LightGray
    ) {
        Text(item)
    }
}
s

Stylianos Gakis

10/18/2022, 8:43 AM
Can you try by providing a unique key as the second parameter to
items
? In your case it’s just strings so maybe there’s no unique key there, but maybe if you use itemsIndexed and use the index as the unique key, like
Copy code
LazyVerticalGrid(...) {
  itemsIndexed(
    asd,
    { index, _ -> index }
  ) {
    DummyCard(...)
  }
}
In general, if you don’t give a key to items on a list, you’re not guaranteed that things will not carry over to other calls when you add/remove items from composition just like that.
a

abu naser

10/18/2022, 9:14 AM
in my original project it's a data class that comes from room database . and also not to mention if I have only click able modifier or one click parameter in the card then everything works fine . anyway I fixed it by using launch effect . I have to mutable boolean state and one change when I click the button and another is when I long press the button . and its works now . and it's also work when I use my custom Staggered grid . so I think it's a problem with Foundation lazy api .
s

Stylianos Gakis

10/18/2022, 9:58 AM
Before doing all this, did you try providing it with a unique key? If it comes from a database that's even better since you can use the PK of that item to provide aa key for the items() API.
The lazy APIs will try to reuse stuff, so if you don't provide it with a way to differentiate between the items by using a unique key, it can mess things up. Just like if you got a ViewHolder in a RV and you hold internal state which is reused when new items enter the RV.
a

abu naser

10/18/2022, 10:21 AM
will try that ..
@Stylianos Gakis no good . i try a data class to represent unique item . try running it and add some item then remove some . you will notice that due to removing of some item the items change position but click listener stays . here is the sample . ( did i do anything wrong in code ? )
Copy code
@Composable
fun ProblemOfLazyClickable() {
    var counter by remember { mutableStateOf(1) }
    val dummyItemList = remember {
        mutableStateListOf("dummy init")
    }
    val dummyItemList2 = remember {
        mutableStateListOf(Test(0,"init item"))
    }
    Column(modifier = Modifier.fillMaxSize()) {
        Button(onClick = {
            dummyItemList.add("dummy item $counter")
            dummyItemList2.add(Test(counter,"dummy item $counter"))
            counter++
        }) {
            Text(text = "Add Dummy")
        }
        Button(onClick = {
            dummyItemList.remove(dummyItemList.random())
            dummyItemList2.remove(dummyItemList2.random())
        }) {
            Text(text = "Remove random Dummy")
        }
        LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 100.dp)) {
            itemsIndexed(dummyItemList2, { index, _ ->
                index
            }, contentType = { _: Int, item: Test -> item }){index: Int, item: Test ->
                DummyCard2(item = item, onClick = {
                    Log.d("dummyEvent","Clicked $it")
                }, onLongClick = {
                    Log.d("dummyEvent","Long Clicked $it")
                })
            }
        }
    }

}

data class Test(
    val id:Int,
    val text:String
)
@Composable
fun DummyCard2(item:Test,onClick:(Test)->Unit ,onLongClick:(Test)->Unit) {
    Card(modifier = Modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onLongPress = { onClick(item) },
                onPress = { onLongClick(item) }
            )
        }
        .padding(4.dp)
        .sizeIn(100.dp, 200.dp, 100.dp, 200.dp)
        //clickable works fine
//        .clickable { onClick(item) }
    , backgroundColor = Color.LightGray
    ) {
        Text(item.text)
    }
}
s

Stylianos Gakis

10/18/2022, 11:16 AM
Yeah in this case it is keyed on the index, which means that it will still try to re-use items with the same index, and this is something that will happen. If you got 4 items, and remove the 3rd one, the 4th item will take the place of the third one with the old key therefore it might reuse the old item’s information. (Not sure exactly how this works but I know that it can lead to problems potentially like the one you’re facing too) In this case instead of passing
{ index, _ -> index }
as the key, go back to just
items
instead of the indexed variant, and do
{ item -> item.id }
as the key. So the items themselves will retain their ID and that will be the unique identifier to that item
a

abu naser

10/18/2022, 12:20 PM
now its works .. thanks for assist .
s

Stylianos Gakis

10/18/2022, 12:55 PM
Glad to hear! Always remember with these lazy APIs. If it helps you, think like you’re in a RecyclerView and items are reused, so keying them properly is #1 priority to not get problems like these 💪
69 Views