Skip to content

fix(android): preserve camera updates during long presses - #4358

Open
RobJacobson wants to merge 4 commits into
rnmapbox:mainfrom
RobJacobson:codex/issue-2738-long-press-camera
Open

RobJacobson wants to merge 4 commits into
rnmapbox:mainfrom
RobJacobson:codex/issue-2738-long-press-camera

Conversation

@RobJacobson

@RobJacobson RobJacobson commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

MapView.onLongPress fires while the finger is still down. On Android, a continuing move gesture can compete with a camera command started by that callback (#2738).

onMapLongClick → remember the held touch
<Camera> → RNMBXMapView.cameraWillMoveMap() (#4371)
OnMoveListener.onMove → handle movement from that touch until it ends
next touch → move normally

This revision rebases onto current main and uses #4371's shared camera hook. It removes the calls in CameraUpdateQueue, moveBy, and scaleBy, along with the synthetic ACTION_CANCEL and dispatchTouchEvent override. A small Android unit test covers the suppression state. The Camera > Long Press Camera example remains; the iOS XCUITest/Xcode changes, Python touch runner, and CONTRIBUTING section are removed. The annotation-drag early return remains in place.

Evidence

  • Before: The original Android control kept the gesture active after a camera command; scaleBy stayed at zoom 16 instead of reaching 17.
  • After: On the Android emulator, the same held-finger sequence passed flyTo, eased and immediate setCamera, moveBy, and scaleBy. The completed camera center stayed stable through release; scaleBy reached zoom 17. A long press without a camera command still panned, and a fresh touch panned after each camera command. Dragging the example's draggable PointAnnotation moved the marker while the map stayed in place.
  • The full Android native unit suite passed (15 tests). yarn test passed repository lint and all 167 JavaScript tests. yarn generate completed without additional generated changes.

To check manually: run the example, open Camera > Long Press Camera, select a mode, long-press away from center and drift the held finger; then lift and pan again.

Merge Danger

Door: Two-way

Blast Radius: Android map gestures. Suppression is limited to move events from the touch that produced a long press and only after a Camera command. A later touch is distinguished by Android's touch downTime. No public JavaScript API or iOS implementation changes.

@RobJacobson
RobJacobson marked this pull request as ready for review October 15, 2026 03:32

@mfazekas mfazekas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for digging into #2738, it's a real problem and the analysis is thorough. Before we can review it properly, could you rework it a bit:

  1. Please rebase onto main. #4371 added OnCameraWillMoveMapListener / RNMBXMapView.cameraWillMoveMap(), which is called whenever Camera is about to move the map. That's the natural single place to end the long press, so the separate calls in CameraUpdateQueue, moveBy and scaleBy shouldn't be needed.
  2. Please keep the PR to the Android native fix and a small unit test. The Long Press Camera example is fine if you want to keep it. Please drop the iOS XCUITest suite, the Xcode project/scheme changes, the Python touch script and the CONTRIBUTING section. iOS code is unchanged, and we'd rather not maintain a separate test harness for this.
  3. Instead of overriding dispatchTouchEvent and injecting a synthetic ACTION_CANCEL, could you try the SDK's gesture API? An OnMoveListener (mapView.gestures.addOnMoveListener) that returns true from onMove while a camera command started during the long press is running should stop the drift. It avoids fake touch events and is less likely to interfere with other long-press users such as draggable PointAnnotation. If it doesn't work, a short note on why would be great.

- Cancel native long-press gestures before queued camera updates
- Consume remaining touch events until release and restore panning on new touches
- Add regression tests for gesture ownership and camera-update ordering
- Cancel active map long presses before moveBy and scaleBy
- Add a native touch regression covering camera motion and gesture cleanup
- Add the long-press camera example and regenerate its documentation links
- Add a permanent native UI suite for all five long-press camera modes
- Verify relative camera destinations, zoom, release stability, and the next pan
- Add the LongPressCamera Xcode scheme and contributor run instructions
- Route long-press camera takeover through the shared camera hook.

- Suppress only the held touch through the Mapbox move listener.

- Remove the separate iOS and Python test harnesses and keep focused Android coverage.
@RobJacobson
RobJacobson force-pushed the codex/issue-2738-long-press-camera branch from be20e5f to 1bf9d19 Compare October 23, 2026 03:20
@RobJacobson

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I rebased onto current main and revised the Android fix in 1bf9d19:

  1. Long-press camera takeover now runs from RNMBXMapView's OnCameraWillMoveMapListener. The separate queue, moveBy, and scaleBy cancellation calls are gone.
  2. The existing Mapbox OnMoveListener handles movement from the held touch while the Camera command takes over. This passed the emulator regression, so I removed the dispatchTouchEvent override and synthetic ACTION_CANCEL. The listener resets on move end; a new touch is distinguished by its downTime.
  3. I kept the Long Press Camera example and one focused Android unit test. I removed the iOS XCUITest, Xcode project/scheme changes, Python runner, and CONTRIBUTING section.

On the Android emulator, held-finger drift passed flyTo, eased/immediate setCamera, moveBy, and scaleBy (zoom 16 → 17). The camera result stayed stable after release, the next touch panned, and a long press without a camera command still panned. I also dragged the example's draggable PointAnnotation; the marker moved while the map stayed put. The full Android unit suite passed (15 tests), as did repository lint and all 167 JavaScript tests. I updated the PR description with reproduction steps and evidence.

@mfazekas

Copy link
Copy Markdown
Contributor

Thanks, this is much closer to what we wanted. I could reproduce #2738 on main (flyTo/easeTo/moveBy/scaleBy all get cancelled by the held finger) and your change fixes all of them on the emulator. A few small things before merge:

  1. Please rebase on main; docs/examples.json conflicts, so re-run yarn generate.
  2. LongPressCameraGesture keeps longPressTime after a long press where the finger never moved, so a later unrelated Camera move (button, prop change, followUserLocation) still arms the suppression and sets isGestureActive = false. It clears itself on the next touch, but could you reset it when a new touch starts (or otherwise scope it to the current gesture)?
  3. Please drop the newline-only change in CameraUpdateQueue.kt, and the comment in LongPress.js about "the regression test", since that harness is gone.

Note also that #4197 touches the same move listener and always ignores movement after a long press (iOS behaviour); we'll need to reconcile the two.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants