Skip to content
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

[DF] feat(#547): add experimentalScrollPositionManagement support to FlashList #824

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

friyiajr
Copy link
Contributor

@friyiajr friyiajr commented Apr 24, 2023

Description

Fixes: #547

Adds support for maintaining vertical content position in iOS and Android. Also fixes resizing issues when changing device orientation from portrait to landscape

Reviewers’ hat-rack 🎩

Messages Test:

  1. To test this out, paste this code over top of the Messages.tsx
    Messages.tsx.txt
  2. Add a lot of messages to the window until you feel there is a couple viewports worth
  3. Scroll up halfway
  4. Keep adding messages
  5. The list should maintain the position of the content

Twitter Test:

  1. Add the experimentalScrollPositionManagement property to the FlashList in Twitter.tsx
  2. Scroll half way down the timeline
  3. Switch the orientation
  4. Slowly scroll up
  5. You should have smooth scrolling and no more jumps

Screenshots or videos (if needed)

iOS Demo

Messages:

Simulator.Screen.Recording.-.iPhone.14.Pro.-.2023-04-24.at.18.36.26.mp4

Twitter:

Screen.Recording.2023-04-24.at.6.38.56.PM.mov

Android Demo

Messages:

a_messages.mov

Twitter:

a_twitter.mov

Checklist

@friyiajr friyiajr changed the title [DF] feat(#547): add experimentalScrollPositionManagement support to FlashList [DF] feat(#547): add experimentalScrollPositionManagement support to FlashList Apr 24, 2023
@geo-vi
Copy link

geo-vi commented Apr 29, 2023

keep it up! we need this merged asap! 🔥 🚀

@DLkhazan
Copy link

would some be kind enough to approve this?

@DLkhazan
Copy link

@naqvitalha would you please approve this?

@HuuNguyen312
Copy link

How can I install flash-list from this branch
https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid

@aweffr
Copy link

aweffr commented May 5, 2023

does this require react native version greater than some number?

I tried this on our 0.62.3 project and got
'_reactNative.ScrollView.render().type' TypeError, seems that this version doesn't support code in

https://github.com/friyiajr/flash-list/blob/6dbf5d7f55849ad115081d3b0dbefc26508178f9/src/BiDirectionalScrollView.tsx

// ...
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const ScrollViewRNRaw: Component<ScrollViewProps> = ScrollViewRN.render().type;
// ...

@perunt
Copy link

perunt commented May 20, 2023

Super excited about this!

I've been testing out this change for a while now, and it's been mostly awesome. But when I tried to scroll while adding a new item, I noticed some jitters. I made a quick video to show what's going on. In the video, you'll notice I'm also firing off a new message every second.
Honestly, I caught the worst scenario in that video. Sometimes, the jitters only happened while scrolling. So, yeah, that's what's been happening.

telegram-cloud-document-2-5424748569082999196.mp4

@terrysahaidak
Copy link

terrysahaidak commented Jun 2, 2023

Tested this PR on iOS and it seems like it does not work, at least in my case.

I'm implementing similar to Instagram navigation where user needs to navigate from a grid to a particular item in a list.
Since scrollToIndex doesn't really work, i tried to render only items i need and the item i navigate to being the first one, and after the mount I request the animation frame and set rest of the data, and since it basically appends everything at the top, maintainVisibleContentPosition does the trick. But experimentalScrollPositionManagement seems to be not working in this case:

  const { data } = useListData()

   const [dataToDisplay, setDataToDisplay] = useState(() => {
     return data.slice(route.params.activeItemIndex, data.length)
   })

  useEffect(() => {
    requestAnimationFrame(() => {
      setDataToDisplay(data)
    })
  }, [])

I use @react-navigation/native-stack if this makes any difference.

PS. Great job so far, exciting for this feature to be part of flash-list. Let me know if you need more info on this.

@rkstar
Copy link

rkstar commented Jun 8, 2023

this PR has been open for 6 weeks now. is anyone going to review it?

@naqvitalha
Copy link
Collaborator

Folks, apologies for the delay on this feature. I'm gonna review this tomorrow! This might have some bugs but we'll continue to address and make it perfect overtime.

/** Checks for overlaps or gaps between adjacent items and then applies a correction (Only Grid layouts with varying spans)
* Performance: RecyclerListView renders very small number of views and this is not going to trigger multiple layouts on Android side. Not expecting any major perf issue. */
fun clearGapsAndOverlaps(sortedItems: Array<CellContainer>) {
fun clearGapsAndOverlaps(sortedItems: Array<CellContainer>, scrollView: ScrollView, maintainTopContentPosition: Boolean) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the flag can be maintained as a variable within this class and final diff can be applied by AutoLayout. That will keep this class independent of view layer like it used to be,

@@ -15,13 +17,21 @@ class AutoLayoutShadow {
private var lastMaxBound = 0 // Tracks where the last pixel is drawn in the visible window
private var lastMinBound = 0 // Tracks where first pixel is drawn in the visible window

private var isInitialRender = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we need the initial render for? A comment would be helpful

View,
} from "react-native";

import { BidirectionalFlatlist } from "./BidirectionalFlatlist";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call it something else? I got confused if we're using FlatList :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does BidirectionalList sound?

this.props.experimentalScrollPositionManagement &&
this.props.renderScrollComponent
) {
throw new CustomError(ExceptionList.customMaintainScrollNotSupported);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if people can still use custom scroll component by using DoubleSidedScrollView instead of ScrollView in their component? In that case we can export DoubleSidedScrollView from FlashList too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be possible. I think it makes sense to restrict users from doing this for now though. This feature is experimental at the moment and its probably easier to maintain if we restrict consumers of the library to using our implementation of the DoubleSidedScrollView for now.

If this implementation proves to be stable, then I think we can allow users to customize as they feel appropriate. What are your thoughts?

@@ -382,7 +398,10 @@ class FlashList<T> extends React.PureComponent<
itemAnimator={this.itemAnimator}
suppressBoundedSizeException
externalScrollView={
renderScrollComponent as RecyclerListViewProps["externalScrollView"]
this.props.experimentalScrollPositionManagement &&
Platform.OS === "android"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the filers in config folder to add platform check. We discourage inline platform checks.

@@ -500,6 +522,10 @@ class FlashList<T> extends React.PureComponent<
...getCellContainerPlatformStyles(this.props.inverted!!, parentProps),
}}
index={parentProps.index}
stableId={
/* Empty string is used so the list can still render without an extractor */
this.props.keyExtractor?.(parentProps.data, parentProps.index) ?? ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we skip this if the flag is off?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, in Objective-C its always expecting a stable id to have some value.

@rkstar
Copy link

rkstar commented Jun 21, 2023

cc @friyiajr - seems like fairly minor comments. this is a huge feature to get shipped to the community.

@tauntmachine
Copy link

can we please ship this feature asap? @naqvitalha @friyiajr

@HuuNguyen312
Copy link

@naqvitalha @friyiajr .

@DLkhazan
Copy link

How can I install flash-list from this branch
https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid

any success on that?

@HuuNguyen312
Copy link

How can I install flash-list from this branch
https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid

any success on that?

You can try
yarn add @huunguyen312/flash-list

@DLkhazan
Copy link

How can I install flash-list from this branch
https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid

any success on that?

You can try yarn add @huunguyen312/flash-list

unable to build after this. any work arounds?

@HuuNguyen312
Copy link

How can I install flash-list from this branch
https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid

any success on that?

You can try yarn add @huunguyen312/flash-list

unable to build after this. any work arounds?

You need to replace @shopify/flash-list to @huunguyen312/flash-list

@DLkhazan
Copy link

How can I install flash-list from this branch
https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid

any success on that?

You can try yarn add @huunguyen312/flash-list

unable to build after this. any work arounds?

You need to replace @shopify/flash-list to @huunguyen312/flash-list

okay, I got it running. But did the experimentalScrollPositionManagement worked on android?
I'm adding only one element at the top and its not holding its position.
Any thoughts on that?

@chj-damon
Copy link

@naqvitalha @friyiajr any updates on this PR?

@aweffr
Copy link

aweffr commented Jul 24, 2023

Any updates on this +1 ?

@DLkhazan
Copy link

Any updates on this +1 ?

while this stuck for approval, you can do is append one element at top and then use scrollToIndex and put index = 1. it will maintain its position.
Also if the element hight is known, you should use scrollToOffset and put offset: elementHeight.

@friyiajr friyiajr force-pushed the maintainPositionAndroid branch 3 times, most recently from ddd4aaa to b677969 Compare August 16, 2023 17:51
@aweffr
Copy link

aweffr commented Aug 21, 2023

hope someone to review this and get it merged.

@irion94
Copy link

irion94 commented Aug 24, 2023

Hi there!
Has anyone tested it on Android using horizontal prop?

I found the app crashing due to incorrectness in alShadow.clearGapsAndOverlaps() method.
For my own purposes, I created a patch applying the original implementation of this method for horizontal lists to solve the problem but it's not a solution.

Btw. Those crashes occur even if the experimentalMaintainTopContentPosition prop isn't set.

Maybe I'm doing something wrong?

Below, is the brief of the error.

Thanks!

EDIT: idk why, it started working. 😱
EDIT2: False alarm 😝 , as I said, if experimentalMaintainTopContentPosition isn't set - the horizontal list crash
EDIT3: Also, if we set experimentalMaintainTopContentPosition, but we are using Reanimated.createAnimatedComponent(FlashList) - list items don't show up

java.lang.ClassCastException: com.facebook.react.views.scroll.ReactHorizontalScrollView cannot be cast to android.widget.ScrollView at com.shopify.reactnative.flash_list.AutoLayoutView.fixLayout(AutoLayoutView.kt:76) at com.shopify.reactnative.flash_list.AutoLayoutView.dispatchDraw(AutoLayoutView.kt:35) at android.view.View.updateDisplayListIfDirty(View.java:17297) at android.view.View.draw(View.java:18086) at android.view.ViewGroup.drawChild(ViewGroup.java:3966)

@yayvery
Copy link

yayvery commented Aug 24, 2023

@irion94 +1, also got that error testing this PR on Android in app with horizontal flashlists.

@aweffr
Copy link

aweffr commented Aug 30, 2023

I clone the repo https://github.com/friyiajr/flash-list/tree/maintainPositionAndroid and install it on ios, the FlashList encounter error once rendered.

The error is "TypeError (0 , PlatformHelper_1.getBidirectionalScrollView) is not a function"

This error is located at:
in FlashList (created by UIFlashListMessageContainer)
in RCTView (created by View)
in View (created by UIFlashListMessageContainer)

I fix it by export "getBidirectionalScrollView" function from both PlatformHelper.android.ts and PlatformHelper.ios.ts.

@nandorojo
Copy link

nandorojo commented Sep 19, 2023

I'm willing to sponsor someone at Shopify to review and merge this. cc @naqvitalha feel free to DM me to set this up.

@rkstar
Copy link

rkstar commented Dec 18, 2023

happy holidays everyone! any update on this PR? i wish i could contribute instead of just coming in here and poking, but i literally don't have any more time in my days. 😭

@mafintosh
Copy link

Exciting stuff! Also willing to sponsor someone to get this landed if it helps.

@robertontiu
Copy link

This is so crucial to this library! Would love to see it implemented.

@Freddy03h
Copy link

Hi! Is there something new after one year? Thank you.

@rkstar
Copy link

rkstar commented Apr 25, 2024

please someone review and merge 🙏

@irisjae
Copy link

irisjae commented May 3, 2024

Super excited about this!

I've been testing out this change for a while now, and it's been mostly awesome. But when I tried to scroll while adding a new item, I noticed some jitters. I made a quick video to show what's going on. In the video, you'll notice I'm also firing off a new message every second. Honestly, I caught the worst scenario in that video. Sometimes, the jitters only happened while scrolling. So, yeah, that's what's been happening.
telegram-cloud-document-2-5424748569082999196.mp4

This is a limitation of the approach taken in this patch. Since recyclerlistview, which is used by flash-list, is responsible for layouting the list items, and recyclerlistview does not really support maintaining a "visible position" in its layouting algorithm, these issues cannot be solved without reimplementing the layout algorithm in recyclerlistview.

I have done so, and it seems to solve most issues that I have with flash-list, including choppy scrolling on bad size estimates, inaccurate scrollToIndex, and maintaining positions of visible items across data changes. I'll package it up in a bit after I clean the repo a bit more, and would be happy if you guys would want to test it.

@irisjae
Copy link

irisjae commented May 8, 2024

As I've alluded to, to maintain visible content position properly is related to choppy scrolling on bad size estimates and also inaccurate programmatic scrolling, and requires deeper changes to fix.

I've published an analysis of this problem and a patch here; feel free to test it out.

Note that instead of attempting to replicate ScrollView's maintainVisibleContentPosition prop, my patch creates a new prop preserveVisiblePosition instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

maintainVisibleContentPosition Not Working