mattinger
03/28/2025, 4:58 PMfun Modifier.onItemImpression(
state: LazyListState,
key: Any = true,
onImpression: (Int) -> Unit
): Modifier
I'm writing an extension to it only do the given callback a single time for each encountered value. My question is should the extended modifier by "composed" or have an @Composable annotation on the function itself?
Is there an advantage to one over the other?
@Composable
fun <T> onlyOnce(callback: (T) -> Unit): (T) -> Unit { ... }
@Composable
fun Modifier.onItemFirstImpression(
state: LazyListState,
key: Any = true,
onImpression: (Int) -> Unit
): Modifier = onItemImpression(
state = state,
key = key,
onImpression = onlyOnce { onImpression(it) }
)
fun Modifier.onItemFirstImpression(
state: LazyListState,
key: Any = true,
onImpression: (Int) -> Unit
): Modifier = composed {
onItemImpression(
state = state,
key = key,
onImpression = onlyOnce {
onImpression(it)
}
)
}
Louis Pullen-Freilich [G]
03/28/2025, 5:11 PMcomposed
is discouraged for performance reasons, @Composable
modifier factory is preferred if you need to use something from composition.
It’s not clear from the snippet, but you might not even need a @Composable
factory here if the logic inside onlyOnce
can be implemented inside a Modifier.Node implementationLouis Pullen-Freilich [G]
03/28/2025, 5:11 PMmattinger
03/28/2025, 6:03 PMmattinger
03/28/2025, 6:06 PMLouis Pullen-Freilich [G]
03/28/2025, 6:08 PMmattinger
03/28/2025, 6:11 PMfun Modifier.onItemImpression(
state: LazyListState,
key: Any = true,
onImpression: (Int) -> Unit
): Modifier {
return composed {
val flow = snapshotFlow { state.layoutInfo }
val visibleItemIndexes = remember { mutableStateOf(emptyList<Int>()) }
LaunchedEffect(key1 = key) {
flow.collect { layoutInfo ->
val newVisibleItemIndexes = layoutInfo.visibleItemsInfo.filter { item ->
item.offset >= layoutInfo.viewportStartOffset && item.size + item.offset <= layoutInfo.viewportEndOffset
}.map { item -> item.index }
val newlyVisible = newVisibleItemIndexes - visibleItemIndexes.value
newlyVisible.forEach {
onImpression(it)
}
visibleItemIndexes.value = newVisibleItemIndexes
}
}
Modifier
}
}
The intent here is to fire a callback whenever an item in a LazyListState becomes newly available in the viewport. So if you are viewing indexes 1 -20, when you scroll and items 11-30 are now visible you'd get callbacks for items 21-30.Louis Pullen-Freilich [G]
03/28/2025, 6:12 PMmattinger
03/28/2025, 6:12 PMmattinger
03/28/2025, 6:13 PMLouis Pullen-Freilich [G]
03/28/2025, 6:15 PMLouis Pullen-Freilich [G]
03/28/2025, 6:16 PM@Composable fun OnItemImpression(...)
mattinger
03/28/2025, 6:16 PMonImpression
modifier that's similar, but meant for things that are in a standard Row or Column and uses LocalView.current inside the composed blockmattinger
03/28/2025, 6:21 PMLouis Pullen-Freilich [G]
03/28/2025, 6:24 PMmattinger
03/28/2025, 6:27 PM