I am looking for an efficient way to trigger a callback for each item of a LazyColumn
as they become visible, but only once.
- The callback should happen only once as items become visible. It should not trigger if the user scrolls past the same item several times.
- The callback should only happen once per each item.
Is there a way Compose-y way of handling this?
I tried to use snapshotFlow
as below, but no matter which side effect I use, it gets triggered over and over as a user scrolls.
val listState = rememberLazyListState()
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo}
.map { it.first() }
.collect {
MyAnalyticsService.someVisibleItemCallback()
}
}
Another way I can image is baking this into the model state as follows.
data class SomeObject(
val someStuff: SomeStuff,
val isSeen: Boolean = false
)
How can I handle this in an efficient way?
CodePudding user response:
Just change your code to :
snapshotFlow { listState.layoutInfo.visibleItemsInfo}
.map { it.first() }
.distinctUntilChanged()
.collect {
MyAnalyticsService.someVisibleItemCallback()
}
Distinct until changed will prevent your flow from being called until your value changes
CodePudding user response:
LazyColumn
behaves the way RecyclerView
does.
When a LazyColumn
item
is "recycled", everything about it will be re-initialized including all side-effects
and its keys
I had a similar requirement and attempted to utilize rememberUpdatedState
, sadly to no avail, it didn't satisfy what I wanted because what ever I do, LazyColumn's item
keeps being recycled, so I just ended up adding an additional attribute to my data class, something that would "persist" outside of the recycling
like your isSeen
boolean property.
isInitialized: Boolean
Making sure this flag wraps my callback.
@Composable
fun ListItemComposable(
item: Item,
doneInitCallback : (Item) -> Unit
) {
LaunchedEffect(Unit) {
if (!item.isInitialized) {
doneInitCallback(item)
}
}
....
}
If there are other ways, I'm not sure, though the closest solution we can find is using either rememberUpdatedState
, your attempt to use snapShotFlow
or rememberSaveable
, but again every item
is being recycled
as you scroll. I haven't tried using rememberSaveable
yet for this situation though.
Also have a look at Phil Dukhov's answer.