-
Notifications
You must be signed in to change notification settings - Fork 24.3k
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
Add maintainVisibleContentPosition support on Android for legacy renderer #35049
Conversation
Base commit: 8c2a4d0 |
Base commit: 8c2a4d0 |
47f93e1
to
c069ae3
Compare
PR build artifact for c069ae3 is ready. |
PR build artifact for c069ae3 is ready. |
@skinsshark has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
I have tested this PR out in my app (closed-source) which is using |
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.
Please take a look at the comment and make sure there's no race possible here.
Rect newFrame = new Rect(); | ||
firstVisibleView.getHitRect(newFrame); | ||
|
||
if (mHorizontal) { |
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.
If you could get rid of the branching logic for horizontal vs vertical scroll view, that would be great!
Branching and interface are two different ways we handle variants. Adding branching will usually be cleaner, but will create duplicate code easily. On the other hand, using interface to abstract the logic will be more challenging in coding, but the result is more flexible.
You may use ReactScrollViewHelper as an example. There's no branching there so duplication is reduced.
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.
Do you have an example of the interface you have in mind?
My thinking when organizing the code like this was to reduce duplication between the 2 implementations while also moving all code related to maintain visible content position out of the scrollview classes to avoid making them too big. The common logic when the variant specific is also very intertwined in some cases, see https://github.com/facebook/react-native/pull/35049/files/c069ae3e39595e7d7e7a3259c6004d1313ad6ba8#diff-b0c8b7c192810637724d9d2ed35db2b8921c45e153ebbda1ef8f34cb53837473R153 for example.
if (deltaX != 0) { | ||
int scrollX = mScrollView.getScrollX(); | ||
mScrollView.scrollTo(scrollX + deltaX, mScrollView.getScrollY()); | ||
mPrevFirstVisibleFrame = newFrame; |
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.
If I understand correctly, the newFrame
is the frame for the new firstVisibleView
, and you've updated the scroll position back to the previous first visible frame. Why do you need to update the previous frame here?
I thought you want to keep the scroll position unchanged, which means you would preserve the first visible frame?
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 to avoid adjusting the scroll position multiple times. I had cases where this method would be called multiple times in a row.
@@ -107,6 +111,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView | |||
private PointerEvents mPointerEvents = PointerEvents.AUTO; | |||
private long mLastScrollDispatchTime = 0; | |||
private int mScrollEventThrottle = 0; | |||
private @Nullable View mContentView; | |||
private @Nullable MaintainVisibleScrollPositionHelper mMaintainVisibleContentPositionHelper = null; |
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.
By default it will be null, so this is technically not needed (https://stackoverflow.com/questions/16699593/uninitialized-object-vs-object-initialized-to-null)
return; | ||
} | ||
|
||
View firstVisibleView = mFirstVisibleView.get(); |
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.
updateScrollPosition
is getting called on layout change. Is this always going to happen after computing mFirstVisibleView
, which here it does in listener to willDispatchViewUpdates
?
In theory I think it should, as we will dispatch the mounting instruction in JS thread or UI thread (when sync update) before the native platform to update the layout. However, since we are doing the computing on UI thread asynchronously, I am not sure if there will be a race condition where the first visible view is computed after we called this method.
cc @javache what do you think?
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.
If willDispatchViewUpdates is called from ui thread (which I’m not sure if it ever does) the dispatch to ui thread will be done synchronously by runOnUIThread. Ideally we could have a way to observe before / after views are updated on the ui thread but wanted to avoid adding more methods to UIManagerObserver for old arch. In my tests this always worked so I was fine with this solution.
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.
@janicduplessis firstVisibleView
may be null, i ran into.
E/unknown:ReactNative: Exception in native call java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.getHitRect(android.graphics.Rect)' on a null object reference at com.facebook.react.views.scroll.MaintainVisibleScrollPositionHelper.updateScrollPositionInternal(MaintainVisibleScrollPositionHelper.java:107) at com.facebook.react.views.scroll.MaintainVisibleScrollPositionHelper.updateScrollPosition(MaintainVisibleScrollPositionHelper.java:97) at com.facebook.react.views.scroll.ReactScrollView.onLayoutChange(ReactScrollView.java:1128) at android.view.View.layout(View.java:20717) at android.view.ViewGroup.layout(ViewGroup.java:6198) at com.facebook.react.uimanager.NativeViewHierarchyManager.updateLayout(NativeViewHierarchyManager.java:254) at com.facebook.react.uimanager.NativeViewHierarchyManager.updateLayout(NativeViewHierarchyManager.java:222)
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.
I see, yes it is missing a null check, I'll open a PR to fix it.
any progress on this? |
PR build artifact for 2764db0 is ready. |
PR build artifact for 2764db0 is ready. |
@skinsshark has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@skinsshark @ryancat Any updates on this? I think the only unaddressed point is #35049 (comment). |
Need an update on this please. |
Any update? 🙏 |
ANy update on this? |
hey all! thank you for your patience and sorry for the delay, there are a couple things we want to test internally but haven't had the bandwidth just yet- i will get this prioritized and be back with updates! happy new year! |
@pull-bot could yours rebuild the tarball in CircleCI ?, cause it had been removed after 30days , thx~~~ |
Hi, many happy returns and thanks for the update. |
I would like to know that as well |
Thanks @skinsshark !! |
facebook/react-native#35049 added support for this on Android
* Update platform label for `maintainVisibleContentPosition` facebook/react-native#35049 added support for this on Android * Update scrollview.md
So this maintanVisibleContentPosition prop is now available on what version of RN? And is available on FlatList as well, right? |
It is available in 0.72. It also works with FlatList but there are a few known issues that will be fixed in #35993 which isn’t merged yet. |
Summary: This adds support for `maintainVisibleContentPosition` on Android. The implementation is heavily inspired from iOS, it works by finding the first visible view and its frame before views are update, then adjusting the scroll position once the views are updated. Most of the logic is abstracted away in MaintainVisibleScrollPositionHelper to be used in both vertical and horizontal scrollview implementations. Note that this only works for the old architecture, I have a follow up ready to add fabric support. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [Android] [Added] - Add maintainVisibleContentPosition support on Android Pull Request resolved: facebook#35049 Test Plan: Test in RN tester example on Android https://user-images.githubusercontent.com/2677334/197319855-d81ced33-a80b-495f-a688-4106fc699f3c.mov Reviewed By: ryancat Differential Revision: D40642469 Pulled By: skinsshark fbshipit-source-id: d60f3e2d0613d21af5f150ca0d099beeac6feb91
@janicduplessis I have tried it in 0.72-rc06. Everything seems fine but I found a problem with my chat app with scenario:
If all messages are short content then maintainVisibleContentPosition is working fine. But messages from 35-31 is very long text (greater than 3000 char) then FlatList still auto scroll to first position. Do you have any suggestions for me? |
…38891) Summary: `mFirstVisibleView` is a weak ref so it can also be null when dereferencing. This was reported on the original PR here #35049 (comment) ## Changelog: [ANDROID] [FIXED] - Fix null crash when using maintainVisibleContentPosition on Android Pull Request resolved: #38891 Test Plan: Not sure exactly in what cases this can happen, but the fix is trivial and makes sense. Reviewed By: cortinico Differential Revision: D48192154 Pulled By: rshest fbshipit-source-id: 57a38a22a0e216a33603438355bde0013c014fbf
…38891) Summary: `mFirstVisibleView` is a weak ref so it can also be null when dereferencing. This was reported on the original PR here #35049 (comment) ## Changelog: [ANDROID] [FIXED] - Fix null crash when using maintainVisibleContentPosition on Android Pull Request resolved: #38891 Test Plan: Not sure exactly in what cases this can happen, but the fix is trivial and makes sense. Reviewed By: cortinico Differential Revision: D48192154 Pulled By: rshest fbshipit-source-id: 57a38a22a0e216a33603438355bde0013c014fbf
@hantrungkien did you ever experiment further with that? thanks! |
We also have noticed some issues on android, I will look at it soon. |
@janicduplessis Following up in case this is helpful, I can open a new issue too: https://snack.expo.dev/@haileyok/flatlist-repro
|
Thanks, this will help, planning to look at it this week. |
No need for an issue. |
@haileyok, have you tried adding half the number of initial items? Also, as you mentioned, the |
my experience was that the number of initial items didn't affect anything. IIRC, even with just one initial item (and some padding on the I suppose my immediate question would be is this something that's a bug/could be fixed or is this a limit of the virtualization (I don't know all the specifics on how the virtualization works, just a quick review of the code here)? I'm implementing a workaround right now to artificially "load in" items when we scroll up, which is a fine solution (and maybe better anyway for performance so we don't immediately load a bunch of items) but would still be interested in knowing if this could be fixed. Appreciate it! |
For more context: Usually the problem with maintainVisibleContentPosition and virtualization is that We added some mitigations to try and prevent this from happening, but for some reason it seems to still happens some times on Android. |
@haileyok I just realized the issue you mentionned happens on iOS. It actually looks like an issue I addressed recently. Sadly I don't think the changes haven't made it into a release yet. Can you try to apply the changes from this 3ed4bf9. This code should be in the @react-native/virtualized-lists package. |
The issue we are currently experiencing seems to happen only on Android if adding items at the start of the list during scrolling. |
Ah, awesome! I'll look later if I have time today or tomorrow if not and let you know. Appreciate the quick response. I take it this addresses both platforms since it's just the JS side? |
@haileyok It does yes |
There is also one more case of sliding that I know of. It happens when items size vary a lot. Internally VirtualizedList uses the average size of previously measured items to estimate the size of new items being added before they get measured and the actual size can be used. In your repro if I increase number of item added to 60 it starts happening. |
I'm running into this as well unfortunately. Do you have an issue or PR tracking this, or will you keep us posted here? Just want to stay updated for when it's fixed 💜 |
@miafoo Can't give you any updates on that status, but what I can pass over is a bit of how we worked around the problem. One way that you might be able to get around this though is to only add items to the top of the list in increments. If you use |
awesome! Can 70.0.x still be supported? The react-native upgrade version is too difficult... |
Summary
This adds support for
maintainVisibleContentPosition
on Android. The implementation is heavily inspired from iOS, it works by finding the first visible view and its frame before views are update, then adjusting the scroll position once the views are updated.Most of the logic is abstracted away in MaintainVisibleScrollPositionHelper to be used in both vertical and horizontal scrollview implementations.
Note that this only works for the old architecture, I have a follow up ready to add fabric support.
Changelog
[Android] [Added] - Add maintainVisibleContentPosition support on Android
Test Plan
Test in RN tester example on Android
Screen.Recording.2022-10-22.at.00.32.27.mov