Solution 1 :
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
Solution 2 :
When a LazyColumn
item
is “recycled“, the item
will be re-initialized including side-effects
it has.
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.
Problem :
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?
Comments
Comment posted by clamentjohn
But for a large list being scrolled the component will get mounted and unmounted, won’t it. In such a case won’t
Comment posted by Arsh
Yes it will be called again but you change your visibility value will be changed only once. Store visibility for all the items in your VM and simply change it for the first and last time when the item comes in view. The distinctUntilChanged will not execute again and again
Comment posted by Sudhir Singh Khanger
If I store visibility as a Boolean in the data class then what role does snapshotFlow plays as you mentioned in the answer. If I have it saved in the model then I would just check for it and if it is false then fire the LaunchedEffect.
Comment posted by Sudhir Singh Khanger
I am trying to think if
Comment posted by Sudhir Singh Khanger
Why is item.isInitialized check inside the side effect? Should it not be outside? Or maybe I am wrong at least when you are in the view you want to avoid the check on recomposition. Any other problems have you experienced with this approach? Any slowdowns?
Comment posted by z.y
I consider this kind of scenario as a “Side-Effect” that’s why I wrap it inside
Comment posted by z.y
Also thank you if you voted it up, I was waiting for some correction or recommendation from the one who voted it down as I find this something hard to work around with. If I may ask, is the size of your list fixed? or undetermined?, because I have another situation where I just ended up using a
Comment posted by Sudhir Singh Khanger
1. I am not seeing any benefit of snapshotFlow because the LaunchedEffect will execute anyway. So will snapshotFlow.
Comment posted by Sudhir Singh Khanger
Size depends on API response but except more or less 100 items. But downside of Column is that 100 columns will be created and there is recycling of them.