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

Fix ScrollView interactions with Android's CoordinatorLayout #44099

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kkafar
Copy link
Contributor

@kkafar kkafar commented Apr 15, 2024

Summary:

Hello, I'm recently researching & implementing native material 3 bottom sheets, toolbars etc. and hitting the wall with various interactions with react-native's ScrollView. Here's one example that's documented on SO: link, but there at least few others related to gestures & dragging (undraggable bottom sheet when scrollViewContentLength < scrollViewHeight, happy to elaborate here). This comes down to the fact, that standard android.widget.ScrollView does not work well with CoordinatorLayout. Therefore I'm coming forward with question whether you would consider inheriting after androidx.core.widget.NestedScrollView which solves aforementioned issues (and mostlikely many others related to interaction with CoordinatorLayout). I want to point out that even Android docs & source code suggest to use NestedScrollView for vertical scrolling in lieu of ScrollView.

Happy to iterate here, would love some guidance especially in testing. I'm also open for implementing NestedScrollingChild / NestedScrollingParent / ... interfaces if you don't think change in inheritance chain is possible.

Changelog:

[ANDROID] [FIXED] - ReactScrollView interactions with CoordinatorLayout

Test Plan:

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Apr 15, 2024
@kkafar kkafar changed the title WIP Fix ScrollView interactions with Android CoordinatorLayout Fix ScrollView interactions with Android CoordinatorLayout Apr 15, 2024
@kkafar kkafar changed the title Fix ScrollView interactions with Android CoordinatorLayout Fix ScrollView interactions with Android's CoordinatorLayout Apr 15, 2024
@janicduplessis
Copy link
Contributor

I also experimented with integrating CoordinatorLayout in RN screens previously and this was a needed change to make it work.

@grahammendick
Copy link
Contributor

I got the ScrollView working with the CoordinatorLayout in the Navigation router.

If you set nestedScrollEnabled on the ScrollView then this will get you most of the way there. The only remaining problem is when the scroll view content is less than the scroll view height. I fixed this by manually dispatching nested scroll events. I put this fix in years ago so I can't be sure what the problem is, but I've a vague memory that React Native blocks the touch events from bubbling.

@javache
Copy link
Member

javache commented Apr 16, 2024

Thanks for the PR! I'd be inclined to not switch to NestedScrollView without a deeper understanding of the changes in behaviours involved.

The other suggestions made here make sense, and would be happy look at a PR that fixes the issue of missing event when scroll view content is less than the scroll view height.

@kkafar
Copy link
Contributor Author

kkafar commented Apr 16, 2024

I would love some testing guidance (maybe issues with behaviour I should put particular emphasis to test?) & more precise description of what do I need to provide you with to change this inclination (if thats feasible). Right now I haven't noticed any unusual behaviour after applying suggested changes (beside fixed interactions with CoordinatorLayout)

Here's potentially useful diff of ScrollView vs NestedScrollView implementation, but haven't got time to analyse differences yet (surely will report after I do).

As for attempts to patch the implementation w/o inheriting from NestedScrollView I'm down for trying out patching this some time next week. Will report back once I do.

@alanleedev
Copy link
Contributor

alanleedev commented Apr 25, 2024

I think argument for NestedScrollView is valid but I don't think we can judge if it is safe to swap the implementation for Android's ReactScrollView just by looking at the diff of Android's ScrollView vs NestedScrollView.
We need a way to make sure that this won't introduce any behavior changes for existing Android apps, but not sure if we have or could come up with such complete set of tests that could make this guarantee.

@ferrannp
Copy link
Contributor

ferrannp commented May 22, 2024

@alanleedev @javache currently, the nestedScrollEnabled is not working properly (if no enough content, the prop is broken). Now, you already have this in your code:

https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Components/ScrollView/ScrollView.js#L1693C1-L1697C41

  render(): React.Node | React.Element<string> {
    const [NativeDirectionalScrollView, NativeDirectionalScrollContentView] =
      this.props.horizontal === true
        ? NativeHorizontalScrollViewTuple
        : NativeVerticalScrollViewTuple;

So you have ScrollView for vertical scroll and HorizontalScrollView for horizontal.

In Android docs, it says this for vertical scroll:

image

And also:

NestedScrollView is just like ScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android. Nested scrolling is enabled by default.

So I think this PR is completely justified.

Another alternative could be:

      this.props.nestedScrollEnabled === true ? NativeNestedVerticalScrollView
      this.props.horizontal === true
        ? NativeHorizontalScrollViewTuple
        : NativeVerticalScrollViewTuple;

But is it worth to write another implementation (NativeNestedVerticalScrollView) if we follow Android recommendation? Anyways, both ways would work the same. But I believe we can go one way or another instead of leaving nestedScrollEnabled as it is (broken).

grahammendick added a commit to grahammendick/navigation that referenced this pull request Jun 25, 2024
Couldn't scroll in a bottomsheet or sheet because the coordinator was intercepting it. This interception is a workaround for [a (react native or android?) bug](facebook/react-native#44099) where the nested scroll stops working of scroll content shorter than height.
Checked if the event target is a sheet and don't intercept in that case.
@grahammendick
Copy link
Contributor

grahammendick commented Jun 30, 2024

I refreshed my memory on this one. The Android ScrollView returns false from OnInterceptTouchEvent when the content is less than the height. So OnTouchEvent isn't called and the ScrollView doesn’t dispatch nested scroll events.

@kkafar could you check whether NestedScrollView returns true from OnInterceptTouchEvent when the content is less than the height, please?

If the NestedScrollView does return true, I think we can say that this is an Android bug with enabling nested scrolling on a regular ScrollView. Then we can go with the approach proposed by @ferrannp

@alanleedev
Copy link
Contributor

Hey folks, thanks for the suggestions. The proposals do seem to make sense but as a framework we need to be bit more careful about the changes and the impact. I'm trying to have more internal discussion on this and will try to give an update next week.

@facebook-github-bot
Copy link
Contributor

@alanleedev has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@alanleedev
Copy link
Contributor

I got the ScrollView working with the CoordinatorLayout in the Navigation router.

If you set nestedScrollEnabled on the ScrollView then this will get you most of the way there. The only remaining problem is when the scroll view content is less than the scroll view height. I fixed this by manually dispatching nested scroll events. I put this fix in years ago so I can't be sure what the problem is, but I've a vague memory that React Native blocks the touch events from bubbling.

@grahammendick @kkafar If we can fix the case for scroll view content is less than the scroll view height then would this be a sufficient fix for now before we migrate to NestedScrollView ?

@grahammendick
Copy link
Contributor

@alanleedev Yep, that would work

@kkafar
Copy link
Contributor Author

kkafar commented Jul 3, 2024

I'll be able to devote my time to do that (try to patch current scrollview) somewhere early next week, if that's ok I volunteer.

@alanleedev
Copy link
Contributor

alanleedev commented Jul 3, 2024

I'll be able to devote my time to do that (try to patch current scrollview) somewhere early next week, if that's ok I volunteer.

@kkafar That would be great. Thanks.

@alanleedev
Copy link
Contributor

I'll be able to devote my time to do that (try to patch current scrollview) somewhere early next week, if that's ok I volunteer.

@kkafar Any updates on this?

kkafar added a commit to software-mansion/react-native-screens that referenced this pull request Aug 10, 2024
## Description

This PR introduces series of features & changes:

1. possibility of specifying custom detents for form sheets on devices
with iOS 16 or newer,
2. changes existing form sheet API of `Screen` component (namely types
of values accepted),
3. Android form sheets (bottom sheets presented in current presentation
context (in iOS terms) with dimming view with configurable interaction.
The form sheet supports up to three detent levels with additional option
of `fitToContents`
4. Android Footer component that works together with `formSheet`
presentation style
5. 🚧 Android modal bottom sheet - similar to `formSheet`, however the
sheet is mounted under separate window.
6. 🚧 iOS Footer component - similar to Android
7. Usage of Material 3
8. series of new props allowing for: 
a. controlling style of the `Screen` component (necessary workaround for
issue with flickering on iOS,
b. controlling whether the screen fragment of particular screen should
be unmounted or not on Android when the screen is on JS stack but not
visible (necessary to achieve "staying form sheet" when navigating back
to a screen with presented form sheet),
c. listening for `sheetDetentChange` events, in case of Android stable &
dragging states are reported, in case of iOS only stable states
  d. todo: describe rest

## Changes


## Known issues

1. [x] ~After recent commits - iOS compilation on Fabric~
2. [ ] Android: issue with nested scrollview - invalid behaviour when
there is not enough content for scrollview to scroll (viewport is >=
content size). Solvable by patching react-native:
facebook/react-native#44099, no other workaround
found. There is one approach [suggested by
grahammendick](https://github.com/grahammendick/navigation/blob/916688d267bd3fc520e2e22328b6aa66124f52ed/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/CoordinatorLayoutView.java#L96-L148),
however yet untested.
3. [ ] Android 'modal' presentation can crash randomly (unknown reason
yet, can be deffered)

## Test code and steps to reproduce

I've used & extended `Test1649` to present all capabilities of new API.

## Checklist

- [ ] Included code example that can be used to test this change
- [ ] Updated TS types
- [ ] Updated documentation: <!-- For adding new props to native-stack
-->
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [ ] Ensured that CI passes
@alanleedev
Copy link
Contributor

comment from @kkafar on linked PR:

no other workaround found. There is one approach suggested by grahammendick, however yet untested.

@kkafar
Copy link
Contributor Author

kkafar commented Sep 3, 2024

Hey, sorry (!) for a such long delay. PTO + always something higher in backlog. I do have still this on my radar, as I need to fix this behaviour to land with stable react-native-screens 4.0. I'm not declaring any specific date but it is pretty close to the top of by backlog right now.

@alanleedev
Copy link
Contributor

@kkafar Thanks for the update.

ja1ns pushed a commit to WiseOwlTech/react-native-screens that referenced this pull request Oct 9, 2024
## Description

This PR introduces series of features & changes:

1. possibility of specifying custom detents for form sheets on devices
with iOS 16 or newer,
2. changes existing form sheet API of `Screen` component (namely types
of values accepted),
3. Android form sheets (bottom sheets presented in current presentation
context (in iOS terms) with dimming view with configurable interaction.
The form sheet supports up to three detent levels with additional option
of `fitToContents`
4. Android Footer component that works together with `formSheet`
presentation style
5. 🚧 Android modal bottom sheet - similar to `formSheet`, however the
sheet is mounted under separate window.
6. 🚧 iOS Footer component - similar to Android
7. Usage of Material 3
8. series of new props allowing for: 
a. controlling style of the `Screen` component (necessary workaround for
issue with flickering on iOS,
b. controlling whether the screen fragment of particular screen should
be unmounted or not on Android when the screen is on JS stack but not
visible (necessary to achieve "staying form sheet" when navigating back
to a screen with presented form sheet),
c. listening for `sheetDetentChange` events, in case of Android stable &
dragging states are reported, in case of iOS only stable states
  d. todo: describe rest

## Changes


## Known issues

1. [x] ~After recent commits - iOS compilation on Fabric~
2. [ ] Android: issue with nested scrollview - invalid behaviour when
there is not enough content for scrollview to scroll (viewport is >=
content size). Solvable by patching react-native:
facebook/react-native#44099, no other workaround
found. There is one approach [suggested by
grahammendick](https://github.com/grahammendick/navigation/blob/916688d267bd3fc520e2e22328b6aa66124f52ed/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/CoordinatorLayoutView.java#L96-L148),
however yet untested.
3. [ ] Android 'modal' presentation can crash randomly (unknown reason
yet, can be deffered)

## Test code and steps to reproduce

I've used & extended `Test1649` to present all capabilities of new API.

## Checklist

- [ ] Included code example that can be used to test this change
- [ ] Updated TS types
- [ ] Updated documentation: <!-- For adding new props to native-stack
-->
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [ ] Ensured that CI passes
@grahammendick
Copy link
Contributor

Here's some more evidence supporting migrating to NestedScrollView. NestedScrollView has better edge-to-edge support than ScrollView. Setting its clipToPadding property to false gives the result below (the image below is taken from the android edge-to-edge doc). See how the content scrolls behind the navigation bar while the last item sits above the navigation bar when fully scrolled.

edge-to-edge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants