fix(Android): cancel button handlers when a native view takes the touch lock - #4433
Conversation
…ch lock `Pressable` (and every component built on the native button) keeps its `NativeViewGestureHandler` running when a native view grabs the touch lock, because that handler is attached with `ACTION_TYPE_NONE` and `cancelAllLegacyHandlers` only cancelled the action-driven ones. The visible symptom is a phantom press: a finger put down on a list row to stop a fling makes the enclosing `ScrollView` claim the touch (Android's `ScrollView` intercepts the `ACTION_DOWN` while the scroller is running and calls `requestDisallowInterceptTouchEvent`), yet the button handler survives, ends on the finger lift and dispatches `onPress` for a row the user never meant to tap. Cancel `NativeViewGestureHandler`s attached with `ACTION_TYPE_NONE` too. The root view's own handler shares that action type but is not a `NativeViewGestureHandler`, so it keeps running and interception is unaffected. Renamed the method since it no longer cancels only the legacy handlers.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR refines Android gesture-handler cancellation when a native view takes the touch lock by replacing “cancel all legacy handlers” behavior with a more targeted cancellation API and logic.
Changes:
- Renamed the orchestrator cancellation method and updated the root helper to call the new method.
- Expanded cancellation logic to also cancel
NativeViewGestureHandlerinstances withACTION_TYPE_NONE(e.g., button-managed handlers) when they would otherwise continue running.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt | Updates root helper to call the renamed/more specific orchestrator cancellation method. |
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt | Renames and documents cancellation method; adjusts predicate to also cancel certain NativeViewGestureHandlers. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| * the button handler and fires a press. The root view's own handler shares that action type and | ||
| * must keep running, hence the type check rather than a plain [GestureHandler.ACTION_TYPE_NONE] | ||
| * one. | ||
| */ |
| * the button handler and fires a press. The root view's own handler shares that action type and | ||
| * must keep running, hence the type check rather than a plain [GestureHandler.ACTION_TYPE_NONE] | ||
| * one. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review. 📝 WalkthroughSummary by CodeRabbit
WalkthroughChangesNative gesture cancellation
Possibly related PRs
Suggested reviewers: Merge Risk: ⚪ Minimal · up to This localized Android change prevents unintended presses when a native view takes the touch lock; no actionable merge-blocking risk remains beyond normal checks and review. 🚥 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 |
Description
Fixes #4432
On Android, a
PressablefiresonPresswhen the touch was only meant to stop a fling: put a finger down on a list row while the list decelerates, lift it without moving, and the row under the finger gets pressed.Pressablewithout relation props rendersPressableWithTouchable, which presses natively throughButtonViewGroup. TheNativeViewGestureHandlerthe button manages for itself is attached withACTION_TYPE_NONE, so it isn't cancelled when a native view takes the touch lock:RNGestureHandlerRootHelper.requestDisallowInterceptTouchEvent()callscancelAllLegacyHandlers(), which only cancels the action-driven handlers (JS_FUNCTION_OLD_API,JS_FUNCTION_NEW_API,REANIMATED_WORKLET,NATIVE_ANIMATED_EVENT).Android's
ScrollViewintercepts theACTION_DOWNwhile its scroller is still running and callsrequestDisallowInterceptTouchEvent(true)up the tree, so that path does run — the button handler just isn't part of what it cancels. It then reachesSTATE_ENDon the finger lift and dispatches the press. The framework's ownACTION_CANCELdoesn't reach the button either, since RNGH delivers touches itself and ignoresonInterceptTouchEvent.This cancels
NativeViewGestureHandlers attached withACTION_TYPE_NONEas well. The root view's handler shares that action type but is not aNativeViewGestureHandler, so it keeps running and interception is unaffected.The v2
Pressablewent throughGestureDetector, so its handlers carried a JS action type and were cancelled by this very path —LegacyPressableandStatefulPressable(any relation prop) are unaffected today, which is a decent A/B when checking the fix.I also renamed
cancelAllLegacyHandlerstocancelHandlersLosingToNativeGesture, since it no longer cancels only the legacy handlers — happy to drop the rename if you'd rather keep the diff minimal.Test plan
Repro app: a
FlatList(fromreact-native) whose rows are wrapped inPressablefromreact-native-gesture-handler, each row logging itsonPress.Before:
onPressfires for the row under the finger.After: nothing fires, and a deliberate tap on a settled list still presses normally.