[Android] Stop nested scroll when a native handler's gesture ends - #4492
[Android] Stop nested scroll when a native handler's gesture ends#4492m-bert wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 SummarySummary by CodeRabbit
WalkthroughThe Android native view gesture handler now conditionally stops nested scrolling when an active gesture ends or is cancelled. ChangesNested scroll lifecycle
Sequence Diagram(s)sequenceDiagram
participant NativeViewGestureHandler
participant ScrollViewHook
participant ScrollView
NativeViewGestureHandler->>ScrollViewHook: shouldStopNestedScroll()
ScrollViewHook-->>NativeViewGestureHandler: true
NativeViewGestureHandler->>ScrollView: stopNestedScroll() on ACTION_UP or cancellation
Suggested reviewers: Merge Risk: ⚪ Minimal · up to Android ScrollView and FlatList pull-to-refresh gestures now release nested scrolling when they end or are cancelled, allowing RefreshControl to complete normally. The change is scoped to opted-in ScrollView handling and is ready to merge. 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped, opt-in via hooks, and matches Android’s expected nested-scroll cleanup timing for end-of-gesture paths.
Pull request overview
This PR fixes an Android-specific interaction bug where ScrollView/FlatList from RNGH used with React Native’s RefreshControl can leave nested scrolling “open” after the gesture ends, preventing SwipeRefreshLayout from receiving onStopNestedScroll and thus delaying/losing onRefresh.
Changes:
- Stop nested scrolling (
View.stopNestedScroll()) after delivering the terminalACTION_UPto the native view when theNativeViewGestureHandleris active and the view’s hook opts in. - Also stop nested scrolling after delivering the synthetic
ACTION_CANCELwhen the handler is cancelled/failed while active. - Introduce a new hook opt-in (
NativeViewGestureHandlerHook.shouldStopNestedScroll()), enabled forScrollViewHook.
File summaries
| File | Description |
|---|---|
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt | Adds an opt-in path to mirror View.dispatchTouchEvent cleanup by calling stopNestedScroll() at the end of an active native-view gesture (notably for ScrollViews). |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Description
ScrollViewandFlatListfrom Gesture Handler never fireonRefreshwhen given React Native'sRefreshControlon Android. The spinner follows the pull but stays parked on release, and the refresh only triggers on the next touch anywhere on the screen.With a
refreshControlRN wraps the scroll view in aSwipeRefreshLayoutand enables nested scrolling on it. The pull reaches the layout through the nested-scroll API, and the layout finishes it (fires refresh or snaps back) only inonStopNestedScroll. Android callsstopNestedScroll()fromView.dispatchTouchEventat the end of a gesture, but onceNativeViewGestureHandleractivates it delivers touches straight to the view'sonTouchEvent, so that cleanup never runs. The nested scroll stays open and the layout is released one touch late, by the CANCEL our root view dispatches on the next DOWN. Gesture Handler's ownRefreshControlavoids this because its handler drives theSwipeRefreshLayoutin touch-drag mode, which finishes the spinner inonTouchEventwithout relying on the nested-scroll cleanup.The handler now calls
stopNestedScroll()on the view after feeding it the final UP or the synthetic CANCEL, mirroringView.dispatchTouchEvent. It only does so while active, since below that the view still receives the events through regular dispatch, and only for views whose hook opts in through the newshouldStopNestedScroll().ScrollViewHookopts in.Fixes #4485
Test plan
ScrollView+ RNRefreshControl, GHFlatList+ RNRefreshControl, andGesture.Native()around an RNScrollView+ RNRefreshControl.onRefreshfires on every pull and the spinner retracts.ScrollView+ GHRefreshControland RNScrollView+ RNRefreshControlunchanged.ScrollViewinside GHScrollView, with and without an RNRefreshControlon the outer: scroll handover, fling, and pull-to-refresh from inside the inner list work the same as with RN scroll views.Repro