Prevent LazyColumn from flashing each time- data changes?

84 views Asked by At

I'm using the paging3 library with Compose Multiplatform. It's working, but I've noticed that my component that consumes the paging data is re-rendered (visible flash) each time new paging data is received.

I believe this is because of the when statement that checks for loading or error states.

I would like to add distinctUntilChangedBy to filter by events.loadstate.refresh, but it isn't available on the flow before the call to collectAsLazyPagingItems.

  • How should I modify this code so that it doesn't re-render each time the number of message events changes?
  • I want to just animate the changes in the list without having the whole component render, which is causing a noticeable flash right now?

This is basically a chat list. Each time a new chat message is received, it is inserted into the database and the paging data is invalidated to load the latest message.

@Composable
fun MessageList(
    pager: Flow<PagingData<MessageEvent>>?,
    modifier: Modifier = Modifier,
) {
    pager?.let { flow ->
        val events = flow.collectAsLazyPagingItems()
        when (events.loadState.refresh) {
            LoadStateLoading -> {
                CenteredProgressIndicator()
            }
            is LoadStateError -> {
                val errorState = events.loadState.refresh as LoadStateError
                ShowError(errorState)
            }
            else -> {
                if (events.itemCount > 0) {
                    LazyColumn {
                        items(events.itemCount) { index ->
                            events[index]?.let {
                                MessageRow(it)
                            }
                        }
                    }
                } else {
                    NoMessagesAvailable(modifier)
                }
            }
        }
    }
}
1

There are 1 answers

0
BenjyTec On

The flickering occurs because for a very short moment, you set the loadState to LoadStateLoading while refreshing the list. Thus, for a very short moment, the CenteredProgressIndicator() is shown.

You haven't provided your code where you set the loadState. But what you could do to resolve this:

  • only show CenteredProgressIndicator() at the initial loading (when the list data is still empty)
  • when refreshing when there already is data in the list, instead show another loading indicator above the list like in PullRefresh:

pull to refresh example

In order to achieve this behaviour, you can adapt your logic and introduce another LoadState:

  • LoadStateLoading for initial loading when the list is empty
  • LoadStateRefreshing for refreshing when there is already data in the list

Then depending on the LoadState, display an according loading animation:

when (events.loadState.refresh) {
    LoadStateLoading -> {
        CenteredProgressIndicator()
    }
    is LoadStateError -> {
        // ...
    }
    else -> {
        // Code for SwipeRefresh
        var refreshing by remember { mutableStateOf(false) }
        val state = rememberPullRefreshState(refreshing)
           
        if (events.loadState.refresh.equals(LoadStateRefresh) {
            refreshing = true
        }

        Box() {
            LazyColumn(Modifier.fillMaxSize()) {
                // your LazyColumn
            }

            // small circular loading indicator
            PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
        }
        
    }
}

With this code, you should be able to use the PullRefresh loading indicator without the pulling functionality.

Note that I am not able to test the code right now, but it should give you the idea.