Search code examples
androidandroid-jetpack-composeandroid-jetpack-compose-list

An efficient way to check when a specific LazyColumn item comes into view?


I need to check when a certain LazyColumn item comes into view, and once it does, make a callback to onItemWithKeyViewed() only once to notify that this item has been viewed. My attempt:

@Composable
fun SpecialList(
    someItems: List<Things>,
    onItemWithKeyViewed: () -> Unit
) {
    val lazyListState = rememberLazyListState()

    if (lazyListState.isScrollInProgress) {
        val isItemWithKeyInView = lazyListState.layoutInfo
            .visibleItemsInfo
            .any { it.key == "specialKey" }
        if (isItemWithKeyInView) {
            onItemWithKeyViewed()
        }
    }

    LazyColumn(
        state = lazyListState
    ) {
        items(items = someItems) { itemData ->
            ComposableOfItem(itemData)
        }
        item(key = "specialKey") {
            SomeOtherComposable()
        }
    }
}

Issue with my method is I notice the list scrolling performance degrades badly and loses frames. I realize this may be because it's checking all visible item keys on every frame?

Also, onItemWithKeyViewed() is currently being called multiple times instead of just the first time it's viewed.

Is there a more efficient way to make a single callback to onItemWithKeyViewed() only the first time "specialKey" item is viewed?


Solution

    1. In such cases, when you have a state that is updated often, you should use derivedStateOf: this will cause recomposition only when the result of the calculation actually changes.
    2. You should not call side effects (which is calling onItemWithKeyViewed) directly in the composable builder. You should use one of the special side-effect functions instead, usually LaunchedEffect - this ensures that the action is not repeated. You can find more information on this topic in Thinking in Compose and in side-effects documentation.
    val isItemWithKeyInView by remember {
        derivedStateOf {
            lazyListState.isScrollInProgress &&
                    lazyListState.layoutInfo
                        .visibleItemsInfo
                        .any { it.key == "specialKey" }
        }
    }
    if (isItemWithKeyInView) {
        LaunchedEffect(Unit) {
            onItemWithKeyViewed()
        }
    }