androidandroid-jetpack-composeandroid-jetpack-compose-listandroid-jetpack-compose-lazy-column

Execute LaunchedEffect only when an item is visible in LazyColumn


I am using a LazyColumn and there are several items in which one of item has a LaunchedEffect which needs to be executed only when the view is visible.

On the other hand, it gets executed as soon as the LazyColumn is rendered.

How to check whether the item is visible and only then execute the LaunchedEffect?

LazyColumn() {
    item {Composable1()}
    item {Composable2()}
    item {Composable3()}
.
.
.
.
    item {Composable19()}
    item {Composable20()}

}

Lets assume that Composable19() has a Pager implementation and I want to start auto scrolling once the view is visible by using the LaunchedEffect in this way. The auto scroll is happening even though the view is not visible.

  LaunchedEffect(pagerState.currentPage) {
    //auto scroll logic
  }

Solution

  • If you want to know if an item is visible you can use the LazyListState#layoutInfo that contains information about the visible items. Since you are reading the state you should use derivedStateOf to avoid redundant recompositions and poor performance

    To know if the LazyColumn contains an item you can use:

    @Composable
    private fun LazyListState.containItem(index:Int): Boolean {
    
        return remember(this) {
            derivedStateOf {
                val visibleItemsInfo = layoutInfo.visibleItemsInfo
                if (layoutInfo.totalItemsCount == 0) {
                    false
                } else {
                    visibleItemsInfo.toMutableList().map { it.index }.contains(index)
                }
            }
        }.value
    }
    

    Then you can use:

        val state = rememberLazyListState()
    
        LazyColumn(state = state){
           //items
        }
    
        //Check for a specific item
        var isItem2Visible = state.containItem(index = 2)
    
        LaunchedEffect( isItem2Visible){
            if (isItem2Visible)
                //... item visible do something
            else
                //... item not visible do something
        }
    

    If you want to know all the visible items you can use something similar:

    @Composable
    private fun LazyListState.visibleItems(): List<Int> {
    
        return remember(this) {
            derivedStateOf {
                val visibleItemsInfo = layoutInfo.visibleItemsInfo
                if (layoutInfo.totalItemsCount == 0) {
                    emptyList()
                } else {
                    visibleItemsInfo.toMutableList().map { it.index }
                }
            }
        }.value
    }