Nthily
11/20/2023, 2:05 PMStateFlow
and UI recomposition
. In short, my ViewModel
has three flows:
1. accountFlow
, which is used to fetch the currently logged-in account from the database.
2. timelinePositionFlow
, which is used to retrieve the last browsing position record from the currently logged-in account.
3. timelineFlow
, which contains all posts stored in the database for the currently logged-in account.
The issue I'm facing is that my app can switch between multiple accounts and browse information. Since each account has a stored timeline position, I need to use rememberLazyListState(initialFirstVisibleItemIndex = ...)
to initialize the position of the LazyColumn
. However, if I switch accounts and StateFlow
emits the value of the new account, the rememberLazyListState
won't recomposition. so that the size of the timeline of the new account may only be 20, and the timeline Position Index
of the old account is greater than 20, which will cause a java.lang.IndexOutOfBoundsException
error
Is there a good way to solve this problem?Nthily
11/20/2023, 2:06 PMprivate val activeAccountFlow = accountDao
.getActiveAccountFlow()
.filterNotNull()
.distinctUntilChanged { old, new -> old.id == new.id }
val timelinePosition = activeAccountFlow
.mapLatest { TimelinePosition(it.firstVisibleItemIndex, it.offset) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = TimelinePosition()
)
val timeline = activeAccountFlow
.flatMapLatest { timelineDao.getStatusListWithFlow(it.id) }
.map { splitReorderStatus(it).toUiData().toImmutableList() }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = persistentListOf()
)
ui
val timeline by viewModel.timeline.collectAsStateWithLifecycle()
val timelinePosition by viewModel.timelinePosition.collectAsStateWithLifecycle()
val lazyState = rememberLazyListState(
initialFirstVisibleItemIndex = timelinePosition.index,
initialFirstVisibleItemScrollOffset = timelinePosition.offset
)
LazyColumn { ... }
Albert Chang
11/20/2023, 4:16 PMrememberLazyListState
inside a key(account) { }
.dorche
11/20/2023, 5:14 PM.combine()
could help too - put the 2/3 states into 1 and filter out these half-updates that are causing you problemsColton Idle
11/20/2023, 6:30 PMNthily
11/20/2023, 11:38 PMColton Idle
11/21/2023, 12:59 AMNthily
11/21/2023, 1:00 AMColton Idle
11/21/2023, 1:10 AMColton Idle
11/21/2023, 1:15 AMNthily
11/21/2023, 1:19 AMvide
11/21/2023, 8:24 AMcheckIndexBounds
crash in alpha08 is most likely this: https://issuetracker.google.com/issues/295745063#comment57Nthily
11/21/2023, 9:44 AMlazystate.firstVisibileIndex
, such as saving the position of the timelineColton Idle
11/21/2023, 12:48 PMNthily
11/21/2023, 1:23 PMColton Idle
11/21/2023, 1:35 PMNthily
11/22/2023, 5:47 AMAlbert Chang
11/22/2023, 6:40 AMthe firstVisibleIndex in LazyState does not update.I would assume there's some problem with your code, or maybe your timeline becomes empty temporarily.
However, I have some functionalities that rely on listening toJust add lazy list state as a key., such as saving the position of the timelinelazystate.firstVisibileIndex
Albert Chang
11/22/2023, 6:44 AMdorche
11/22/2023, 2:22 PMactiveAccountFlow
is already private, you should be able to do make timelinePosition
and timeline
flows private too, and only expose one screenState
(for example) StateFlow to Compose to consume, which then should have all the data in a good state, without mismatches.Nthily
11/25/2023, 6:22 AMNthily
11/25/2023, 6:26 AMAlbert Chang
11/25/2023, 9:09 AM```val timeline = activeAccountFlow
.flatMapLatest { timelineDao.getStatusListWithFlow(it.id) }```This introduces some asynchronism and the problem is likely because timeline is updated after the other two (so the UI will first be composed with the old value of timeline and the new values of the other two, and then be composed with all the new values).
Nthily
11/26/2023, 1:19 PMprivate val activeAccountFlow = accountDao
.getActiveAccountFlow()
.filterNotNull()
.distinctUntilChanged { old, new -> old.id == new.id }
val combinedFlow = activeAccountFlow
.flatMapLatest { activeAccount ->
val timelineFlow = timelineDao.getStatusListWithFlow(activeAccount.id)
timelineFlow.map {
UiState(activeAccount.id, it, TimelinePosition(activeAccount.index, activeAccount.offset))
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = null
)
I didn't think it would work out that way 😅Colton Idle
11/26/2023, 3:29 PM