Hi :wave: , I hope one of you can be of help to me...
# compose
n
Hi đź‘‹ , I hope one of you can be of help to me. I have a LazyColumn that gets populated with different sections that contains a LazyRow. Inside that LazyRow I would like to track when a user has been viewing a given element for more than 2 seconds. This works fine By getting the firstVisibleItemIndex
Copy code
val state = rememberLazyListState()
val visibleIndex: Int by remember {
    derivedStateOf {
        state.firstVisibleItemIndex
    }
}
LazyRow that takes the state
Copy code
LazyRow(
    state = state
)..... bla bla
And then a LaunchedEffect on the visibleIndex
Copy code
LaunchedEffect(visibleIndex) {
    delay(2000)
    onMetricViewed(metrics[visibleIndex], visibleIndex)
}
My problem now is that this also runs on the second element in the LazyColumn which is far from visible (maybe around 5%). I've tried with calculating the visibility of items but couldn't come up with something that worked as I wanted it to. How can I make sure that
Copy code
onMetricViewed(metrics[visibleIndex], visibleIndex)
is only called if I can actually see the full height of the element in the LazyRow? 🤔
m
You're using a LaunchedEffect here, and LazyColumn/LazyRow are going to actually add compositions that aren't yet on screen. This is deliberate to avoid jank. So when you have only 1 item visible, there may be several that are already composed and ready to go. As such, the LaunchedEffect is going to run on those items as well. I suspect what you'll need is to use the state to get the range of visible items and fire the launched effect whenever a particular item is within that range of visible items.
n
@mattinger thank you for taking the time. I'm aware but that's why I'm just checking the firstvisibleitemindex. But the problem lies in that the second column is showing partially hence this will also trigger for that one. So either I need to know if the column is fully visible or If the specific item in the row is fully visible. Not just in width but also height.
m
There may be some information in the LazyListState that tells you the bounds of your children. If you can get that, you can capture the bounds of the container (ie, the LazyColumn/LazyRow) using the “onGloballyPositioned” modifier, and see if the bounds of that element fit entirely within the bounds of it’s parent.
It’s going to be somewhat cpu intensive though as you’re probably going to have to track all scroll positioning changes.
But something alone this lines. I make no guarantees it’s foolproof, but it seems to generally work.
Copy code
@Composable
@Preview
fun x() {
    Timber.uprootAll()
    Timber.plant(Timber.DebugTree())

    MaterialTheme {
        Surface(modifier = Modifier.fillMaxSize()) {
            val columnState = rememberLazyListState()
            var columnSize by remember { mutableStateOf(IntSize(0, 0)) }

            LazyColumn(
                modifier = Modifier
                    .fillMaxSize()
                    .onGloballyPositioned {
                        columnSize = it.size
                    },
                state = columnState
            ) {
                (1..100).forEach {
                    item {
                        Text("$it")
                    }
                }
            }

            val layoutInfoFlow = snapshotFlow { columnState.layoutInfo }

            LaunchedEffect(key1 = true) {
                layoutInfoFlow.collect {
                    it.visibleItemsInfo.forEach {
                        val isFullyVisible = (it.offset + it.size) <= columnSize.height
                        Timber.tag("Visibility").d(
                            "Item at ${it.index} fullyVisible: ${isFullyVisible}"
                        )
                    }
                }
            }
        }
    }
}
Of course this will get infinitely more complicated of you have nesting of LazyColumn and LazyRow