Skip to content

[Android] ReanimatedSwipeable: tap-to-close gesture steals presses on renderLeftActions/renderRightActions buttons (detector box doesn't follow the row's transform) #4250

@priemskiyyy

Description

@priemskiyyy

Description

On Android, buttons rendered inside renderRightActions/renderLeftActions of ReanimatedSwipeable never fire onPress in 3.0.0. This affects every button implementation — RNGH Pressable, Touchable, RectButton are deterministically dead; core RN Pressable works only intermittently (it merely races the steal instead of losing outright). iOS is unaffected. This is likely the root cause behind many reports in #3223.

Root cause

ReanimatedSwipeable wraps the row content in a tap-to-close GestureDetector (enabled while the row is open). Three things combine:

  1. Since 3.0, GestureDetector is a host component, and its native view frame is the bounding box of its children's layout frames (RNGestureHandlerDetectorShadowNode.cpp, layout()). The swipeable row moves aside via transform: translateX, which doesn't affect layout — so the tap detector's box stays full-row-sized and, while open, covers the revealed action buttons.
  2. On Android, GestureHandlerOrchestrator.traverseWithPointerEvents has a special case in the BOX_NONE branch that records a detector view's own handlers even when no child is hit: if (found || view is RNGestureHandlerDetectorView) { recordViewHandlersForPointer(...) }. A press on a revealed button is inside the detector's (stale) bounds, so the tap-to-close handler starts tracking it.
  3. On finger-up, a button press is a perfect tap: the tap recognizes, activates first, runs close(), and its activation cancels the button's handler - onPress never fires.

iOS is unaffected because of #3823 (the detector passes hit-testing through its own surface, so its recognizers never track touches over the buttons). v2 was unaffected because the legacy GestureDetector attached the tap directly to the row view, which is translated away and correctly excluded by the transform-aware hit test.

Expected: tapping a button in the revealed actions fires its onPress, as it does on iOS and as it did in 2.x.

Actual: on Android the press never fires - the tap-to-close gesture wins and the row closes instead.

Steps to reproduce

Reproduction

ReanimatedSwipeable + any button in renderRightActions, Android (new architecture):

<ReanimatedSwipeable
  renderRightActions={() => (
    <Touchable onPress={() => console.log('never logs on Android')}>
      <Text>Delete</Text>
    </Touchable>
  )}
>
  <View style={{ height: 64 }} />
</ReanimatedSwipeable>

Swipe open, tap the button -> on Android the tap-to-close gesture wins and the row closes instead; on iOS onPress fires.

Workaround (verified on device)

Shrink the tap-to-close gesture's active area to the visible part of the row with a negative hitSlop inside ReanimatedSwipeable:

const tapGestureHitSlop = useDerivedValue<HitSlop>(() =>
  rowState.value === -1
    ? { right: -rightWidth.value }
    : rowState.value === 1
      ? { left: -leftWidth.value }
      : {}
);

const tapGesture = useTapGesture({
  shouldCancelWhenOutside: true,
  enabled: shouldEnableTap,
  hitSlop: tapGestureHitSlop as unknown as HitSlop,
  ...
});

I know it's not a best solution, but it works (at least in my case)

A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.

https://github.com/priemskiyyy/rngh-v3-swipeable-android-repro

Gesture Handler version

3.0.0

React Native version

0.85.3

Platforms

Android

JavaScript runtime

Hermes

Workflow

Using Expo Prebuild or an Expo development build

Architecture

New Architecture (Fabric)

Build type

Debug mode

Device

Real device

Device model

Samsung Galaxy A14

Acknowledgements

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    Platform: AndroidThis issue is specific to AndroidRepro providedA reproduction with a snack or repo is provided

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions