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

Stylianos Gakis

02/29/2024, 10:46 AM
Is there any possible scenario where you can imagine that this code would crash, because I am experiencing production crashes with
Fatal Exception: java.lang.IndexOutOfBoundsException: index: 2, size: 2
with this snippet
Copy code
val list: ImmutableList<Foo> = TODO() // from parameters
val pagerState = rememberPagerState(pageCount = { list.size })
HorizontalPager(
  state = pagerState,
  key = { index -> list[index].id },
) { page ->
  Ui(page)
}
and the crash specifically happens on
key = { index -> list[index].id },
. How would the key ever try to get an index that is not part of the list passed into the pagerState? The only thing I can imagine happening is somehow the list being changed in a recomposition, the pagerState is updated, but the
key
function still is trying to for some reason fetch the item with the old index? I am asking here to see if anyone else has experienced this before.
👀 1
z

Zach Klippenstein (he/him) [MOD]

02/29/2024, 5:26 PM
any possible scenario where you can imagine that this code would crash
I can imagine a lot of things. There’s one android version where some float functions just don’t work 😛
😅 3
But yea, this code assumes that
rememberPagerState
will update the lambda passed to it on every recomposition, because the first composition will capture the initial list, and when the list changes Compose will create a new lambda, capture the new list, and pass it to
rememberPagerState
. In most composables, that is the expected behavior and the new lambda will be used. However, many times the parameters to
remember*
functions are only used to initialize the state, not keep it updated later, and that might be what’s happening here.
Easy to check, i think this should fix it if that’s the issue:
Copy code
val updatedList by rememberUpdatedState(list)
val pagerState = rememberPagerState(pageCount = { updatedList.size })
s

Stylianos Gakis

03/11/2024, 9:02 AM
It’s been tricky to reproduce this tbh, but yeah the way you put it this makes complete sense. I have been postponing replying here since I don’t know with 100% confidence that this is fixing the problem precisely because I can’t reproduce locally. The implementation I see in the library itself is this:
Copy code
@Composable
fun rememberPagerState(
    initialPage: Int = 0,
    initialPageOffsetFraction: Float = 0f,
    pageCount: () -> Int
): PagerState {
    return rememberSaveable(saver = PagerStateImpl.Saver) {
        PagerStateImpl(
            initialPage,
            initialPageOffsetFraction,
            pageCount
        )
    }.apply {
        pageCountState.value = pageCount
    }
}
So even if the “wrong” lambda is captured inside the
rememberSaveable
in there, which is definitely possible to happen, it looks like the
pageCount
lambda is being updated as it should even outside of the remember block. The implementation is here https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]foundation/pager/PagerState.kt;l=110-117?q=rememberPagerState So I think me doing it as well wouldn’t change something as far as I can tell. Maaaybe this used to not be the same in older versions of the library which meant that it had the bug in the past but not anymore. I will just wait and see if I see this crash again I think now that I understand the potential problem a bit better. Thanks for the help! 😊
s

scana

04/08/2024, 11:07 AM
Managed to reproduce the issue: https://issuetracker.google.com/issues/333209044 Perhaps it's not the HorizontalPager's issue directly, but still looks like an easy mistake to make 🥶
8 Views