Skip to content

fix(android): MarkerView touch/press on new architecture (Fabric)#4176

Open
mfazekas wants to merge 1 commit intomainfrom
fix/markerview-fabric-touch
Open

fix(android): MarkerView touch/press on new architecture (Fabric)#4176
mfazekas wants to merge 1 commit intomainfrom
fix/markerview-fabric-touch

Conversation

@mfazekas
Copy link
Contributor

@mfazekas mfazekas commented Mar 16, 2026

Fixes #3832
Fixes #3934
Fixes #3440
Fixes #3981

Problem

On Android with the new React Native architecture (Fabric/TurboModules), any interactive content inside a MarkerView — buttons, sliders, switches, TextInput — silently failed. Touches appeared to land nowhere, onPress was never fired, and press feedback (opacity, scale) didn't show.

Root cause: Mapbox positions view annotations by calling setTranslationX/Y directly on the native view. This is a render-layer transform — it moves the view visually but does not update the view's Yoga layout position. Fabric's shadow tree only tracks layout position, so UIManager.measure() always returned (0, 0), making every hit-region wrong.

A second issue: the MapView's own gesture recogniser was intercepting MOVE events mid-sequence (after DOWN), sending CANCEL to child Pressables and suppressing onPress even in cases where the hit-test was correct.

iOS is unaffected — Mapbox iOS positions annotations via frame assignment, which Fabric reads correctly.

Fix

Android — RNMBXMarkerViewContent.kt

  • Override setTranslationX/Y to intercept Mapbox's position updates and fire a topAnnotationPosition event to JS.
  • Deduplicate events via lastReportedTx/Ty to prevent a feedback loop when Fabric re-applies the same transform prop back to setTranslationX/Y.
  • Override dispatchTouchEvent to call requestDisallowInterceptTouchEvent(true), preventing the parent MapView pan/zoom gesture from stealing MOVE events and cancelling child touches.

Android — RNMBXMarkerViewContentManager.kt

  • Switch base class to AbstractEventEmitter and register topAnnotationPosition → onAnnotationPosition.

Codegen spec — RNMBXMarkerViewContentNativeComponent.ts

  • Add onAnnotationPosition as a DirectEventHandler so Fabric's bridge knows about it.

JS — MarkerView.tsx

  • Refactor from class to function component.
  • Receive onAnnotationPosition events and apply the translation as a React transform on RNMBXMarkerView, keeping the Fabric shadow tree in sync with Mapbox's actual on-screen position.
  • PixelRatio.get() divide: Android translationX/Y is in device pixels; React transforms expect logical pixels.

Example — MarkerView.tsx

  • Add an InteractiveMarkerContent marker with two sliders, a switch, a counter, a TextInput, and a Pressable button to exercise the full range of complex interactivity inside a MarkerView.

Test plan

  • Open the Marker View example on Android (new arch)
  • Tap the blue button inside the simple marker → onPress fires, blue flash visible
  • Drag the Hue and Opacity sliders in the interactive marker → values update live
  • Toggle the Switch → state flips
  • Tap +/− counter buttons → counter increments/decrements
  • Type in the Note field → keyboard appears, text updates
  • Tap "Pressed N times" button → press count increments, button darkens on press
  • Pan/zoom the map around the markers → map responds normally; markers stay interactive after re-positioning
  • Verify iOS behaviour is unchanged

…ure (Fabric)

On Android with the new React Native architecture, Mapbox positions view
annotations via setTranslationX/Y — a render-layer transform that is invisible
to Fabric's shadow tree. This caused UIManager.measure to return (0,0) for
every MarkerView, so Pressable/TouchableOpacity hit-testing failed, onPress
was never fired, and interactive children (sliders, switches, buttons) didn't
respond to touches.

Fix:
- Override setTranslationX/Y in RNMBXMarkerViewContent to intercept Mapbox's
  position updates and fire a topAnnotationPosition event to JS.
- Deduplicate events to prevent a feedback loop when Fabric re-applies the
  same transform prop back to setTranslationX/Y.
- Override dispatchTouchEvent to call requestDisallowInterceptTouchEvent(true),
  preventing the parent MapView's pan/zoom gesture from stealing MOVE events
  and sending CANCEL to child Pressables.
- In JS (MarkerView.tsx), receive the position event and apply the translation
  as a React transform on RNMBXMarkerView, keeping the Fabric shadow tree in
  sync with Mapbox's native positioning so hit regions are correct.
- Register the event via AbstractEventEmitter in RNMBXMarkerViewContentManager.
- Add an interactive example (sliders, switch, counter, TextInput, Pressable)
  to the MarkerView example to exercise complex interactivity.

iOS is unaffected: Mapbox iOS positions annotations via frame assignment, which
Fabric tracks correctly without any workaround.
@mfazekas mfazekas force-pushed the fix/markerview-fabric-touch branch from 3c4d8d9 to e63f30c Compare March 16, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant