androidkotlinandroid-jetpack-composeandroid-jetpack-compose-listandroid-jetpack-compose-lazy-column

How to implement LazyColumn item impression tracking


I have a lazycolumn with items, and I want to send an event every time one of the items appears on screen. There are examples of events being sent the first time (like here https://plusmobileapps.com/2022/05/04/lazy-column-view-impressions.html) but that example doesn't send events on subsequent times the same item reappears (when scrolling up, for example).

I know it shouldn't be tied to composition, because there can be multiple recompositions while an item remains on screen. What would be the best approach to solve something like this?


Solution

  • I modified example in article from keys to index and it works fine, you should check out if there is something wrong with keys matching.

    @Composable
    private fun MyRow(key: Int, lazyListState: LazyListState, onItemViewed: () -> Unit){
        Text(
            "Row $key",
            color = Color.White,
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Red)
                .padding(20.dp)
        )
    
        ItemImpression(key = key, lazyListState = lazyListState) {
            onItemViewed()
        }
    }
    
    
    @Composable
    fun ItemImpression(key: Int, lazyListState: LazyListState, onItemViewed: () -> Unit) {
    
        val isItemWithKeyInView by remember {
            derivedStateOf {
                lazyListState.layoutInfo
                    .visibleItemsInfo
                    .any { it.index == key }
            }
        }
    
        if (isItemWithKeyInView) {
            LaunchedEffect(Unit) {
                onItemViewed()
            }
        }
    }
    

    Then used it as

    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(14.dp),
        state = state
    ) {
        items(100) {
            MyRow(key = it, lazyListState = state) {
                println("🔥 Item $it is displayed")
                if(it == 11){
                    Toast.makeText(context, "item $it is displayed", Toast.LENGTH_SHORT).show()
                }
            }
    
        }
    }
    

    Result

    enter image description here

    Also instead of sending LazyListState to each ui composable you can move ItemImpression above list as a Composable that only tracks events using state. I put 2, but you can send a list and create for multiple ones either

    @Composable
    private fun LazyColumnEventsSample() {
    
        val context = LocalContext.current
        val state = rememberLazyListState()
    
        ItemImpression(key = 11, lazyListState = state) {
            Toast.makeText(context, "item 11 is displayed", Toast.LENGTH_SHORT).show()
        }
    
    
        ItemImpression(key = 13, lazyListState = state) {
            Toast.makeText(context, "item 13 is displayed", Toast.LENGTH_SHORT).show()
        }
    
    
        LazyColumn(
            verticalArrangement = Arrangement.spacedBy(14.dp),
            state = state
        ) {
            items(100) {
                Text(
                    "Row $it",
                    color = Color.White,
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.Red)
                        .padding(20.dp)
                )
            }
        }
    }