Home > Mobile >  LazyColumn callback for items that become visible but only once
LazyColumn callback for items that become visible but only once

Time:10-22

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.

  • Related