Ali_Waris
Ali_Waris

Reputation: 2382

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
  }

Upvotes: 3

Views: 2884

Answers (2)

Gabriele Mariotti
Gabriele Mariotti

Reputation: 364421

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
}

Upvotes: 1

Johann
Johann

Reputation: 29877

LazyScrollState has the firstVisibleItemIndex property. The last visible item can be determined by:

val lastIndex: Int? = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index

Then you test to see if the list item index you are interested is within the range. For example if you want your effect to launch when list item 5 becomes visible:

val lastIndex: Int = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1

LaunchedEffect((lazyListState.firstVisibleItemIndex > 5  ) && ( 5 < lastIndex)) {
  Log.i("First visible item", lazyListState.firstVisibleItemIndex.toString())

      // Launch your auto scrolling here...
}

LazyColumn(state = lazyListState) {

}

NOTE: For this to work, DON'T use rememberLazyListState. Instead, create an instance of LazyListState in your viewmodel and pass it to your composable.

Upvotes: 2

Related Questions