Khashayar
Khashayar

Reputation: 356

Remember scroll position in lazycolumn?

I am aware of the remember lazy list state and it works fine

 setContent {       
       Test(myList) // Call Test with a dummy list
    }

  @Composable
    fun Test(data: List<Int>){
        val state = rememberLazyListState()

        LazyColumn(state = state) {
            items(data){ item ->Text("$item")}
          }
     }

It will remember scroll position and after every rotation and change configuration it will be the same
But whenever I try to catch data from database and use some method like collectAsState
it doesn't work and it seem an issue

   setContent{
      val myList by viewModel.getList.collectAsState(initial = listOf())
      Test(myList)
   }

Upvotes: 13

Views: 5723

Answers (1)

Mattia Ferigutti
Mattia Ferigutti

Reputation: 3708

Save LazyListState Forever

/**
 * Static field, contains all scroll values
 */
private val SaveMap = mutableMapOf<String, KeyParams>()

private data class KeyParams(
    val params: String = "",
    val index: Int,
    val scrollOffset: Int
)

/**
 * Save scroll state on all time.
 * @param key value for comparing screen
 * @param params arguments for find different between equals screen
 * @param initialFirstVisibleItemIndex see [LazyListState.firstVisibleItemIndex]
 * @param initialFirstVisibleItemScrollOffset see [LazyListState.firstVisibleItemScrollOffset]
 */
@Composable
fun rememberForeverLazyListState(
    key: String,
    params: String = "",
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    val scrollState = rememberSaveable(saver = LazyListState.Saver) {
        var savedValue = SaveMap[key]
        if (savedValue?.params != params) savedValue = null
        val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex
        val savedOffset = savedValue?.scrollOffset ?: initialFirstVisibleItemScrollOffset
        LazyListState(
            savedIndex,
            savedOffset
        )
    }
    DisposableEffect(Unit) {
        onDispose {
            val lastIndex = scrollState.firstVisibleItemIndex
            val lastOffset = scrollState.firstVisibleItemScrollOffset
            SaveMap[key] = KeyParams(params, lastIndex, lastOffset)
        }
    }
    return scrollState
}

Usage:

LazyColumn(
    state = rememberForeverLazyListState(key = "Overview")
)

Click here to check the original answer.

Save ScrollState Forever

Taking inspiration from the previous answer I created a similar remember method to save the ScrollState.

import androidx.compose.foundation.ScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.saveable.rememberSaveable

/**
 * Static field, contains all scroll values
 */
private val SaveMap = mutableMapOf<String, ScrollKeyParams>()

private data class ScrollKeyParams(
  val value: Int
)

/**
 * Save scroll state on all time.
 * @param key value for comparing screen
 * @param initial see [ScrollState.value]
 */
@Composable
fun rememberForeverScrollState(
  key: String,
  initial: Int = 0
): ScrollState {
  val scrollState = rememberSaveable(saver = ScrollState.Saver) {
    val scrollValue: Int = SaveMap[key]?.value ?: initial
    SaveMap[key] = ScrollKeyParams(scrollValue)
    return@rememberSaveable ScrollState(scrollValue)
  }
  DisposableEffect(Unit) {
    onDispose {
      SaveMap[key] = ScrollKeyParams(scrollState.value)
    }
  }
  return scrollState
}

For usage:

val scrollState = rememberForeverScrollState("history_screen")
  
Column(
  modifier = modifier
    .fillMaxSize()
    .verticalScroll(scrollState)
) {
 ...
}

Old Answer

Unfortunately for now there's not a native way to do so, but you can use this code:

val listState = rememberLazyListState()

listState has 3 methods:

  • firstVisibleItemIndex
  • firstVisibleItemScrollOffset
  • isScrollInProgress

All of them are State() so you will always get the data as it updates. For example, if you start scrolling the list, isScrollInProgress will change from false to true.

SAVE AND RESTORE STATE

val listState: LazyListState = rememberLazyListState(viewModel.index, viewModel.offset)

  LaunchedEffect(key1 = listState.isScrollInProgress) {
    if (!listState.isScrollInProgress) {
      viewModel.index = listState.firstVisibleItemIndex
      viewModel.offset = listState.firstVisibleItemScrollOffset
    }
  }

Upvotes: 8

Related Questions