Status Update
Comments
su...@google.com <su...@google.com>
he...@google.com <he...@google.com> #2
Branch: androidx-main
commit 16278c75a09262bfc2b1e230730f79ec5a4d4918
Author: Andrey Kulikov <andreykulikov@google.com>
Date: Tue May 25 18:25:16 2021
Do not reset initial scroll position in LazyColumn/Row when items are loaded asynchronously
If the initial scroll position(or restored scroll position) is not 0 this value will be immediately overridden by 0 if there were no items yet provided for LazyColumn/Row.
It is easily reproducible with Paging where we start loading asynchronously and there is always a first frame when we don't have any items.
Fixes: 177245496
Test: new tests for LazyColumn and LazyRow
Change-Id: I95e374fb06dc480aab7b56b8b3cacb92ca1188a9
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
he...@google.com <he...@google.com> #3
dm...@ownid.com <dm...@ownid.com> #4
Yes, we basically just changed the default behavior for the empty case for now. Please track this bug
he...@google.com <he...@google.com> #5
My workaround currently is to keep `LazyPagingItems` object in `ViewModel`, so that `itemCount` and other state will be the same when navigated back.
dm...@ownid.com <dm...@ownid.com> #6
yes, it is expected as the paging is starting loading asynchronously and there is always at least one frame when there is no items yet
dm...@ownid.com <dm...@ownid.com> #7
he...@google.com <he...@google.com>
ak...@google.com <ak...@google.com> #8
so we can discuss this separately.
Thanks!
ak...@google.com <ak...@google.com> #9
Where can I find the new bug? this issue affect me as well.
ap...@google.com <ap...@google.com> #10
Can you provide an example? In my case stored `LazyPagingItems` object does not want to load data
upd: No longer needed. In my case the issue with LazyVerticalGrid. For myself I found solution - I made copy of LazyPagingItems to make counstructor public and create LazyPagingItems inside ViewModel, so reloading does not occur when navigating backwards
dm...@ownid.com <dm...@ownid.com> #11
ah...@gmail.com <ah...@gmail.com> #12
ah...@gmail.com <ah...@gmail.com> #13
ah...@gmail.com <ah...@gmail.com> #14
In my case .cachedIn(viewModelScope)
preserves data from loading again, but scroll position is still resetting to the start every time I navigate to another composable and back. I use LazyColumn
with header (item
element before items
), so that might be the case
ah...@gmail.com <ah...@gmail.com> #15
Check if you have any other item in the LazyList outside the paged items it will lose position.
If you have any comment it out and see if it still lose position.
ah...@gmail.com <ah...@gmail.com> #16
ah...@gmail.com <ah...@gmail.com> #17 Restricted+
ap...@google.com <ap...@google.com> #18
I have found a workaround that works for me.
The LazyPagingItems.kt
file was copied from the androidx.paging:paging-compose
artifact into my project (it doesn't have any other dependencies).
I modified it to add a flag inside the LazyPagingItems
class:
/**
* The number of items which can be accessed.
*/
val itemCount: Int get() = itemSnapshotList.size
// The code below was added:
/**
* Added to fix scroll state restoration - wait for this flag to be set to true
* before setting your [LazyListState] on the [LazyColumn].
*
* @author Baptiste Candellier
*/
var hasRestoredItems: Boolean = false
private set
The flag is set inside the updateItemSnapshotList()
method
private fun updateItemSnapshotList() {
itemSnapshotList = pagingDataDiffer.snapshot()
// The code below was added:
hasRestoredItems = true
}
Then on my composable, I wait for this flag to become true to pass the "real" LazyListState
, that comes from a ViewModel
.
LazyColumn(
// Wait for the items to be restored to set the real list state, so that it isn't reset in the meantime
state = if (lazyPagingItems.hasRestoredItems) lazyListState else LazyListState()
) {
// ... your items here
There might be edge cases but it works for me, I hope it helps.
ak...@google.com <ak...@google.com> #19
// Save and restore list state
fun lazyListSaver(): Saver<MutableState<LazyListState>, *> = listSaver(
save = { listOf(it.value.firstVisibleItemIndex, it.value.firstVisibleItemScrollOffset) },
restore = {
mutableStateOf(LazyListState(
firstVisibleItemIndex = it[0],
firstVisibleItemScrollOffset = it[1]
))
}
)
// Usage
fun ProfileTab(lazyListState: MutableState<LazyListState>)
LazyVerticalGrid(
state = lazyListState.value,
)
}
Hope this solution works.
sg...@google.com <sg...@google.com>
dm...@ownid.com <dm...@ownid.com> #20
rememberLazyListState() is doing pretty much the same internally. Could you please elaborate a bit more on why the build in solution doesn't work for you?
pr...@google.com <pr...@google.com> #21
Ouch! I'm new on Compose, so don't know how state, rememberLazyListState() works so i directly pass
fun ProfileTab() LazyVerticalGrid( state = rememberLazyListState(), ) }
and while i switch tab from 1->2 and back from 2-> the state of LazyVerticalGrid regenerated, but i didn't try to save in state on globally and pass in composable.
After i seeing this
gu...@gmail.com <gu...@gmail.com> #22
as a lot of people mentioned, LazyColumn can't restore position correct when Paging has a Header
this is because rememberLazyListState can restore position when there are at least 1 element in the list
but if there is a Header or Footer in LazyColumn, then it will restore in first frame when paging's item count is still 0
so I store listState in ViewModel and restore it when Paging item count > 0
val listState = if (pagingData.itemCount > 0) viewModel.listState else rememberLazyListState()
gu...@gmail.com <gu...@gmail.com> #23
Try this workaround, you need to check lazyPagingItems before use:
@Composable
fun MainFrame(lazyPagingItems: LazyPagingItems<PerformanceEvent.Building>) {
val refresh = lazyPagingItems.loadState.refresh
// My temp solution
if (lazyPagingItems.itemCount == 0 && refresh is LoadState.NotLoading ) return //skip dummy state, waiting next compose
LazyColumn { ... }
}
sg...@google.com <sg...@google.com> #24
Here is a workaround:
@Composable
fun <T : Any> LazyPagingItems<T>.rememberLazyListState(): LazyListState {
// After recreation, LazyPagingItems first return 0 items, then the cached items.
// This behavior/issue is resetting the LazyListState scroll position.
// Below is a workaround. More info: https://issuetracker.google.com/issues/177245496.
return when (itemCount) {
// Return a different LazyListState instance.
0 -> remember(this) { LazyListState(0, 0) }
// Return rememberLazyListState (normal case).
else -> androidx.compose.foundation.lazy.rememberLazyListState()
}
}
Should we still expect a fix from any Jetpack library ?
Description
Component used: Credentials
Version used: androidx.credentials:credentials:1.0.0-alpha05
androidx.credentials:credentials-play-services-auth:1.0.0-alpha05
Steps to reproduce:
On Passkeys prompt slider (or on biometry/pin prompt) rotate device (trigger configuration change)
Result: coroutineScope is canceled but credential operation is not, causing:
Expected correct behavior: Passkey operation is canceled correctly: