How to retain scroll position with Jetpack Compose AdapterList?

Viewed 3215

I have an App with a List-Detail flow built with Jetpack Compose. I would like to retain the scroll position when going back from the details view to the list view.

Surfaces are exchanged based on the Model state:

@Composable
private fun AppContent() {
    val scrollerPosition = ScrollerPosition()
    Crossfade(State.currentScreen) { screen ->
        Surface(color = MaterialTheme.colors.background) {
            when (screen) {
                is Screen.List -> ListScreen(scrollerPosition)
                is Screen.Details -> DetailsScreen(screen.transaction)
                is Screen.New -> NewScreen()
            }
        }
    }
}

ListScreen used to have a VerticalScroller and I was giving it a ScrollerPosition to retain the position after screen changes. However, this solution does not work with AdapterList.

This is how it used to be:

@Composable
private fun TransactionsList(modifier: Modifier, scrollerPosition: ScrollerPosition) {
    Box(modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center)) {
        VerticalScroller(scrollerPosition = scrollerPosition) {
            AdapterList(State.transactions, itemCallback = { transaction ->
                TransactionListRow(transaction)
                ListDivider()
            })
        }
    }
}

How can I make AdapterList retain the scroll position?

@Composable
private fun TransactionsList(modifier: Modifier, scrollerPosition: ScrollerPosition) {
    Box(modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center)) {
        AdapterList(State.transactions, itemCallback = { transaction ->
            TransactionListRow(transaction)
            ListDivider()
        })
    }
}
2 Answers

rememberScrollState()?

I think it can be extracted as a value or maybe the lerp() function can be used, by making it a custom Composable instead using Layout()

Why not use lazycolumn instead?

With Jetpack Compose 1.0 scroll position retention has became possible using rememberLazyListState().

To retain the state, initialize the state variable somewhere high enough in the tree, for example, above the navigation. Then pass it to the LazyColumn.

Here is an example with a Crossfade which retains the scroll position in the list when switching between list and details:

  val listState = rememberLazyListState()

  Crossfade(screen) { scr ->
    Surface(color = colors.background) {
      when (scr) {
        is Screen.List -> LazyColumn(state = listState) {
              items(items) { item ->
                // here come the items
              }
            }
        is Screen.Details -> DetailsScreen()
      }
    }
  }

The size of items must stay the same in order for this to work.

Related