fix(android): declare gesture simultaneousness bidirectionally between content pan and scroll#2675
Open
ronnymajani wants to merge 1 commit into
Conversation
…n content pan and scroll On Android, the inner scrollable cannot be scrolled on first sheet open because the content pan gesture cancels the native scroll gesture. The existing code only declares simultaneousness on the scroll side (`Gesture.Native().simultaneousWithExternalGesture(pan)`); the pan does not declare `simultaneousWithExternalGesture(scroll)`. RNGH on Android requires both sides to declare the relation. This change wires the inner scrollable's native gesture back up to `BottomSheetDraggableView` via a new `NativeScrollGestureContext`, so the pan can include it in its `simultaneousHandlers` array. State (not ref) is required, and the setter is made idempotent to avoid a re-render loop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
On Android, the inner scrollable inside a
BottomSheetcannot be scrolled when the sheet is first opened. Dragging vertically inside the scrollable does nothing — the content pan gesture cancels the native scroll on every touch. Curiously, focusing then blurring aTextInputinside the sheet "fixes" the problem for the remainder of that session, after which the scroll works normally.Diagnostic instrumentation showed the symptom precisely:
onScrollBeginDragfires 4× in succession with zeroonScrollevents between them — i.e. RNGH keeps starting the scroll and immediately canceling it in favor of the pan.Root cause
BottomSheetDraggableViewcreates the content pan gesture with:…where
simultaneousHandlersis built fromnativeGestureRef,refreshControlGestureRef, and any user-provided handlers. It does not include the inner scrollable'sGesture.Native().Meanwhile,
createBottomSheetScrollableComponentdoes declare the relation on the scroll side:So the simultaneousness is only declared on one side. RNGH on Android requires the declaration on both sides — on iOS the one-sided declaration appears to be enough, which is why this bug only manifests on Android.
The "focus then blur an input" workaround presumably forces RNGH to re-resolve its internal gesture tree, at which point the declarations end up coordinated.
What this PR does
Introduces a
NativeScrollGestureContextthat lets the inner scrollable register itsGesture.Native()back up to the outerBottomSheetDraggableView. The pan then includes that gesture in itssimultaneousWithExternalGesture(...)array, completing the bidirectional declaration.Three notable implementation choices, each captured in inline JSDoc on the changed files:
1. State, not ref
simultaneousWithExternalGestureresolves its argument at gesture-handler registration time. A ref whose.currentisnullat that moment results in no relation being established, and subsequent ref mutations are not picked up — RNGH does not lazily re-resolve.A
useStateis therefore required: when the inner scrollable mounts and registers its gesture, the resulting state update re-renders theBottomSheet, recomputes the pan'suseMemo, and re-attaches itsGestureDetectorwith the relation now in place.2. Setter is idempotent (first non-null wins)
Without a guard, the setter would loop:
(Reproducible: yields a hard
Maximum update depth exceededafter ~50 iterations.)Once a non-null gesture is registered, subsequent non-null registrations are no-ops. The bidirectional relation only needs to be established once; further updates would only churn the gesture tree without changing semantics.
3. Registration effect has no deps-change cleanup; unmount cleanup lives in its own effect
For the same loop-avoidance reason: nulling the registration on every deps change would re-render the pan without the scroll, then re-register, then re-render the pan with the scroll, ad infinitum. The deps-change branch of the cleanup is dropped; a separate unmount-only effect (using a ref to capture the latest context) still clears the registration when the scrollable fully unmounts (e.g. when the sheet closes).
Files changed
src/contexts/gesture.tsNativeScrollGestureContextwith{nativeScrollGesture, setNativeScrollGesture}. JSDoc explains the why.src/components/bottomSheet/BottomSheet.tsxuseState-backed value and an idempotent setter.src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsxnativeScrollGestureto the pan'ssimultaneousHandlers.src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsxscrollableGesturevia the context on mount; clears on unmount.No public API changes. No type changes. No behavior change for any platform/scenario where the scroll already worked.
Verification
yarn typescript— cleanyarn lint --error-on-warnings— clean on the four modified files (the pre-existing warning insrc/hooks/useBoundingClientRect.tsis onmasterand untouched here)Related
This bug is the underlying cause for several long-standing reports about scroll/pan conflicts in
BottomSheetcontent on Android (notably the "scroll only works after focusing an input" pattern).Happy to break this into smaller commits, adjust the comments, or rework the context shape if you'd prefer a different approach.