Uguzon
Uguzon

Reputation: 73

Scrolling to lazyColumn item with the item key and not the item index

I built a complex lazyColumn composable that mimic a card (indeed, I need to have a list of cards containing potentialy many items each, and nesting a LazyColumn in a Card is not possible (due to the "Vertically scrollable component was measured with an infinity maximum height contraints" issue). So my composable gets a list of data items of this kind :

data class CardsState(val cards: ImmutableList<Card>)

data class Card(
    val header: String,
    val items: ImmutableList<CardItem>,
)

In the LazyColumn, it can takes several LazyListScope.item to draw one CardItem.

So far so good.

But I also need sometimes to scroll to a specific CardItem. Unfortunately, I can't use the LazyListState.animateScrollToItem(index: Int) function as it takes the item index as parameter, and i can't tell what is the lazyColumn item index I need as it can be tricky to calculate.

The best for me would be to have a LazyListState.animateScrollToItem(key: Any) as all my LazyColumn items have a unique key that I can pass as a parameter to my state like this :

interface CollectionItem {
    val key: Any?
}

fun CollectionItem.makeKey(): String = buildString {
    append(javaClass.name)
    key?.let { append("_$it") }
}

data class CardsState(
    val cards: ImmutableList<Card>,
    val scrollToItemWithKey: Any?, // The key of the lazyColumn item I want to scroll to
)

data class Card(
    val header: String,
    val items: ImmutableList<CardItem>,
) : CollectionItem {
    override val key: String = header
}

sealed class CardItem : CollectionItem {
    data class CardItemTypeA(/* some parameters */) : CardItem()
    data class CardItemTypeB(/* some others*/) : CardItem()
}

@Composable
fun MyContent(cardsState: CardsState) {
    val scrollState = rememberLazyListState()
    LazyColumn(state = scrollState) {
        cardsState.cards.forEach { card ->
            item(key = "${card.makeKey()}_top") { Spacer(modifier = Modifier.height(16.dp)) }
            item(key = "${card.makeKey()}_header") { Header(card.header) }
            items(items = card.items, key = { it.makeKey() }) {
                when (it) {
                    is CardItem.CardItemTypeA -> ComposeCardItemTypeA()
                    is CardItem.CardItemTypeB -> ComposeCardItemTypeB()
                }
                item(key = "${card.makeKey()}_bottom") { CardBottomLayout() }
            }
        }
    }
}

But obviously this scrollToItemWithKey doesn't exist. Of course I could "calculate" the lazycolumnIndex knowing how it is built, but that makes my calculation made in a ViewModel dependent on the way the view is built on the compose side, and can be tricky, especially if I want to add other composable within my main LazyColumn. Is there another way for me to achieve this scrolling ?

Upvotes: 1

Views: 205

Answers (1)

tyg
tyg

Reputation: 15579

Of course I could "calculate" the lazycolumnIndex knowing how it is built, but that makes my calculation made in a ViewModel dependent on the way the view is built on the compose side

That calculation doesn't belong in the view model, it should be done somewhere near/in the LazyColumn because it is dependent on how that is constructed. Either create a mapping of CardItem to its index in the final LazyColumn and make sure it is only recalculated when cardsState.cards changes (like, remember(cardsState.cards) { ... } and use this mapping to retrieve the precalculated index when you need to scroll, or calculate the index on demand when you want to execute the scroll.

Upvotes: 0

Related Questions