https://kotlinlang.org logo
#compose
Title
# compose
v

Vaibhav Jaiswal

03/28/2024, 12:18 PM
I have a screen, in which I have a Horizontal pager with 2 pages and each page has a Lazy Column Now If i navigate to some other screen and come back, the unselected page loses its scroll state This also happens if I minimize the app and come back, the selected pages has the scroll state, but the unselected one loses it Does anyone know how to fix this? I'm on Compose 1.5.11 and kotlin 1.9.21 Reproducer
Copy code
@Composable
fun TestPager() {
    val pagerState = rememberPagerState(0){ 2 }
    HorizontalPager(
        state = pagerState,
        modifier = Modifier.fillMaxSize(),
    ){
        TestPage(it)
    }
}

@Composable
fun TestPage(index: Int) {
    val listState = rememberLazyListState()
    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize()
    ){
        items(40) {
            Surface(modifier = Modifier.fillMaxSize()){
                Text(text = "Row $it", modifier = Modifier.padding(16.dp))
            }
        }
    }
}
y

youssef

03/28/2024, 12:20 PM
you can use this
Copy code
/**
 * 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 rememberScrollPositionListState(
    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
}
v

Vaibhav Jaiswal

03/28/2024, 12:23 PM
Thanks for the suggestion, I have already tried something like this, i.e. saving lazy list state for all pages in the parent composable It works, but has some other side effects Scrolling a lot in one page, resets the scroll state of the other page **I'm using Paging3 and both lists originate from the same DB table
s

Sean Proctor

03/28/2024, 12:40 PM
You need to hoist the state to somewhere that is going to stay on the stack. If the pages are affecting each other, you need to investigate that issue separately. I would be concerned about the same state object being used on both pages since you're using it in the same composable with no key.
v

Vaibhav Jaiswal

03/28/2024, 12:42 PM
I have a MapString, LazyListState in my decompose component/viewmodel, The scrolling affecting the other page is some other issue. All these seem to be workarounds. Isn't there any official solution to this bug?
s

Sean Proctor

03/28/2024, 12:45 PM
Without seeing your actual code, it's impossible to guess what the actual problem is.
I would suggest making a minimal reproduction that you would expect to work but exhibits the problem you're having. I wouldn't expect the code you posted to do what you want.
v

Vaibhav Jaiswal

03/28/2024, 12:53 PM
The code i posted is what I want to work I've also tried the workaround of hoisting the state like this in my Decompose Component
Copy code
internal val pages: List<FeedPagesConfig> = listOf(
    FeedPagesConfig.Trending,
    FeedPagesConfig.Latest
)
internal val listStates = pages.associateWith { LazyListState() }
and then fetching this listState in my composable
I get the correct listState and everything works fine, But when I scroll a lot in one page (which loads more pages of data using Paging3) and swipe back to other page the scroll state of this page is reset to 0
s

Sean Proctor

03/28/2024, 1:00 PM
Are the lists using the same data? Check how you handle null values. You need to have some kind of placeholder.
v

Vaibhav Jaiswal

03/28/2024, 1:02 PM
Both are fetched from the same database table I debugged and found when this scroll reset happens The
LazyPagingItems<T>
has
itemCount = 0
and
loadState.mediator = null
s

Sean Proctor

03/28/2024, 1:15 PM
Sounds like there's something else going on there. I would try just hoisting your state to the
TestPager
first, without any saving.
remember { List(2) { LazyListState() } }
and pass the state to
TestPage
by index. If that works, then you'll need to write a saver. https://developer.android.com/reference/kotlin/androidx/compose/runtime/saveable/package-summary#listSaver(kotlin.Function2,kotlin.Function1) Seems pretty straight forward.
2 Views