-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[emojisearch] Preserve scroll position for Android view #1599
Conversation
initialFirstVisibleItemIndex, | ||
) | ||
public fun rememberLazyListState(): LazyListState { | ||
return rememberSaveable(saver = LazyListState.Saver) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change absorbs #1494 (🙏 to @-veyndan)
04f469f
to
431a693
Compare
@@ -108,7 +114,9 @@ private fun LazyColumn( | |||
val labelToUrl = Json.decodeFromString<Map<String, String>>(emojisJson) | |||
|
|||
allEmojis.clear() | |||
allEmojis.addAll(labelToUrl.map { (key, value) -> EmojiImage(key, value) }) | |||
var index = 0 | |||
allEmojis.addAll(labelToUrl.map { (key, value) -> EmojiImage("${index++}. $key", value) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
print out index to make debugging/seeing what's going on with the list easier
test is failing, I'll get to it |
@@ -46,9 +46,8 @@ internal fun LazyList( | |||
) { | |||
val itemProvider = rememberLazyListItemProvider(content) | |||
var lastVisibleItemIndex by remember { mutableStateOf(0) } | |||
val itemsBefore = remember(state.firstVisibleItemIndex) { (state.firstVisibleItemIndex - OffscreenItemsBufferCount / 2).coerceAtLeast(0) } | |||
val itemsBefore = remember(state.scrollItemIndex) { (state.scrollItemIndex - OffscreenItemsBufferCount / 2).coerceAtLeast(0) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooooh I think we might have broken this.
This should probably be a different mutableState for proper on-demand loading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As-is this only updates once, and I think it needs to update with every scroll.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whereas the automated scrolling does not need to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
var placeholderPoolSize by remember { mutableStateOf(20) } | ||
RefreshableLazyList( | ||
isVertical, | ||
itemsBefore = itemsBefore, | ||
itemsAfter = itemsAfter, | ||
onViewportChanged = { localFirstVisibleItemIndex, localLastVisibleItemIndex -> | ||
// Update LazyListState. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Update LazyListState. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scrolling behavior looks great.
Does this break lazy loading?
fixed! |
@@ -13,6 +13,7 @@ kotlin { | |||
sourceSets { | |||
commonMain { | |||
dependencies { | |||
api libs.jetbrains.compose.runtime.saveable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be implementation
if you change the Saver
to be private
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will make a follow up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here it is: #1600
@@ -101,9 +104,11 @@ internal open class ViewLazyList private constructor( | |||
addOnScrollListener( | |||
object : RecyclerView.OnScrollListener() { | |||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { | |||
val viewsOnScreen = recyclerView.children.map { recyclerView.getChildViewHolder(it).bindingAdapterPosition } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a decently expensive operation to perform on every frame of scrolling. It involves allocation of the list and boxing of the position integers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is.
There was an alternative of passing in a mutable of viewHolder and positions to adapter to keep track of positions of visible views (by adding/removing keys when onViewDetachedFromWindow
and onBindViewHolder
), it's somewhat ugly though.
There might be other alternatives.
Previous calls : linearLayoutManager.findFirstVisibleItemPosition()
and linearLayoutManager.findLastVisibleItemPosition()
are not giving the accurate indices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous calls :
linearLayoutManager.findFirstVisibleItemPosition()
andlinearLayoutManager.findLastVisibleItemPosition()
are not giving the accurate indices.
I'd be curious to investigate this more, because it's kinda surprising that a core API would fail like this. Did you find any bug reports on it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you find any bug reports on it?
from my time-boxed googling, there wasn't report with similar symptom
@@ -101,9 +104,11 @@ internal open class ViewLazyList private constructor( | |||
addOnScrollListener( | |||
object : RecyclerView.OnScrollListener() { | |||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { | |||
val viewsOnScreen = recyclerView.children.map { recyclerView.getChildViewHolder(it).bindingAdapterPosition } | |||
userHasScrolled = true // Prevent guest code from hijacking the scrollbar. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean it's impossible to programmatically change the scroll position once the user has interacted with the list a single time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's a good point ... yes, it is at the moment
I haven't properly reviewed the PR yet, but I'm noticing a crash in Emoji Search after these changes:
|
import androidx.compose.runtime.setValue | ||
|
||
@Composable | ||
public fun rememberLazyListState( | ||
initialFirstVisibleItemIndex: Int = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we remove the default value here? It's kinda nice to have it as it aligns with what Compose UI does with their LazyListState
implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the init for initialFirstVisibleItemIndex
now is at down below (to 0)
@@ -101,9 +104,11 @@ internal open class ViewLazyList private constructor( | |||
addOnScrollListener( | |||
object : RecyclerView.OnScrollListener() { | |||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { | |||
val viewsOnScreen = recyclerView.children.map { recyclerView.getChildViewHolder(it).bindingAdapterPosition } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous calls :
linearLayoutManager.findFirstVisibleItemPosition()
andlinearLayoutManager.findLastVisibleItemPosition()
are not giving the accurate indices.
I'd be curious to investigate this more, because it's kinda surprising that a core API would fail like this. Did you find any bug reports on it?
public var firstVisibleItemIndex: Int = 0 | ||
private set | ||
|
||
public fun restoreIndex(index: Int) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we change this back to scrollToItem
? https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListState#scrollToItem(kotlin.Int,kotlin.Int)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function is not really a scroll to item anymore, it has it's own logic, and what it does is "restore when there's criteria are met"
Preserve scroll position works end to end with this PR
scrollposition.mov