MajinKenn
MajinKenn

Reputation: 99

Android Jetpack Compose - Saving a LazyListState's firstVisibleItemIndex in one Composable Function for another Function to scroll to

Scenario: I have two similar Composable functions that display similar lists of data via LazyColumns, each having their own rememberLazyListState variables.

@Composable
fun ListA(listData: List<TypeA>)
{
    LazyColumn {
        items(listData) {
            LazyColumnItem(data: TypeA)
        }
    }
}

@Composable
fun ListB(listData: List<TypeB>) 
{
    LazyColumn {
        items(listData) {
            LazyColumnItem(data: TypeB)
        }
    }
}

Something along those lines; the code is a bit more complicated that this with recomposition after some State values change outside of these composable functions; state that determine which List composable to display, either TypeA or TypeB lists. Assume both lists have the same itemCount or listSize.

While I notice that the list saves its position or state when modifying outside state that causes recomposition with the same composable; modifying state that changes the composable displayed into the other composable (ie, TypeB list composable was displayed using TypeB's composable function, now I want to show TypeA list composable using TypeA's composable function), the list position is reset to 0. This I understand, but I am trying to do it so that I can change the composable function used and scroll to the position the previous list was at.

I've tried creating function parameters to pass the LazyListState's firstVisibleItemIndex to the host function for these composables inside one of Compose's effect blocks, fair warning, I'm still learning Compose and Effect blocks but I'm not sure if this is the correct approach:

LaunchedEffect(key1 = "key) {
    lazyListState.animateScrollToItem(index)
    functionNameToPassIndex(index: Int)
}

The above code block doesn't pass the index up properly to save in a rememberSavable variable holding the index as state.

I guess I'm mainly asking for possible guidance on how to do this, while I'm figuring it out myself too. Any tips or steps would be appreciated over a direct solution.

Upvotes: 1

Views: 1135

Answers (2)

Parniyan
Parniyan

Reputation: 339

Define a rememberSaveable variable to hold the scroll position index.

var scrollPosition by rememberSaveable { mutableStateOf(0) }

Pass these parameters to your lists:

@Composable
fun ListA(listData: List<TypeA>, scrollPosition: MutableState<Int>) {
    val lazyListState = rememberLazyListState(initialFirstVisibleItemIndex = scrollPosition.value)
    
    LaunchedEffect(scrollPosition.value) {
        lazyListState.scrollToItem(scrollPosition.value)
    }
    
    LazyColumn(state = lazyListState) {
        items(listData) { item ->
            LazyColumnItem(data = item)
        }
    }
}

@Composable
fun ListB(listData: List<TypeB>, scrollPosition: MutableState<Int>) {
    val lazyListState = rememberLazyListState(initialFirstVisibleItemIndex = scrollPosition.value)
    
    LaunchedEffect(scrollPosition.value) {
        lazyListState.scrollToItem(scrollPosition.value)
    }
    
    LazyColumn(state = lazyListState) {
        items(listData) { item ->
            LazyColumnItem(data = item)
        }
    }
}

Update the scrollPosition variable to store the current scroll position index.

fun switchToListA() {
    scrollPosition = listBScrollPosition.value
}

fun switchToListB() {
    scrollPosition = listAScrollPosition.value
}

Then use it in your main composable:

@Composable
fun MainComposable() {
    val listAData = //...
    val listBData = //...
    
    var listAScrollPosition by rememberSaveable { mutableStateOf(0) }
    var listBScrollPosition by rememberSaveable { mutableStateOf(0) }
    
    var showListA by remember { mutableStateOf(true) }
    
    if (showListA) {
        ListA(listData = listAData, scrollPosition = listAScrollPosition)
    } else {
        ListB(listData = listBData, scrollPosition = listBScrollPosition)
    }
    
    Button(onClick = {
        showListA = !showListA
        if (showListA) {
            listBScrollPosition = listAScrollPosition
        } else {
            listAScrollPosition = listBScrollPosition
        }
    }) {
        Text("Switch List")
    }
}

Upvotes: 0

Philio
Philio

Reputation: 4185

Have you tried using a single LazyListState for both composables?

In a very simple example such as:

var bool by remember { mutableStateOf(true) }
val listState = rememberLazyListState()

Column(modifier = Modifier.fillMaxSize()) {
    Button(onClick = { bool = !bool }) {
        Text(text = "Swap List")
    }
    if (bool) {
        ListA(listData = items, listState = listState, modifier = Modifier.weight(1f))
    } else {
        ListB(listData = items, listState = listState, modifier = Modifier.weight(1f))
    }
}

It seems to work quite well, even if the items are different sizes.

Upvotes: 1

Related Questions