Skip to content

[Android] NullPointerException in GestureHandler.moveToState when a handler is detached mid-gesture (orchestrator!!) #4567

Closed

Description

@Davide-Michelotti

Description

What happens: on Android the app crashes with a NullPointerException in GestureHandler.moveToState (orchestrator!!) when a gesture handler is detached at the same time as its gesture finishes.

Expected: detaching a handler whose gesture has just ended is a no-op (or a clean cancel), never a crash.

FATAL EXCEPTION: main
java.lang.NullPointerException
  at com.swmansion.gesturehandler.core.GestureHandler.moveToState(GestureHandler.kt:633)
  at com.swmansion.gesturehandler.core.GestureHandler.cancel(GestureHandler.kt:741)
  at com.swmansion.gesturehandler.react.RNGestureHandlerRegistry.detachHandlerInternal$lambda$11(RNGestureHandlerRegistry.kt:138)
  at com.swmansion.gesturehandler.react.RNGestureHandlerRegistry$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
  at android.os.Handler.handleCallback(Handler.java:942)
  at android.os.Looper.loop(Looper.java:288)
  at android.app.ActivityThread.main(ActivityThread.java:7935)

Why (from the 3.3.0 sources): it is a race between RNGestureHandlerRegistry.detachHandlerInternal and the end of the gesture.

  1. detachHandlerInternal checks handler.view != null synchronously (the handler is still prepared, gesture in progress) and posts handler.cancel() with UiThreadUtil.runOnUiThread.
  2. Before the posted runnable runs, the finger is lifted: the gesture ends and the orchestrator calls reset() on the handler, which sets view = null and orchestrator = null; the state goes back to STATE_UNDETERMINED.
  3. The posted cancel() now runs on a reset handler. cancel() accepts STATE_UNDETERMINED, calls moveToState(STATE_CANCELLED), and orchestrator!!.onHandlerStateChange(...) throws.

So the view != null check and the cancel() are not atomic. The same code is in 2.28.0, 2.33.0, 3.3.0 and 3.4.0-nightly-20261002.

We hit it with react-native-sortables (drag & drop grid): on drop the grid re-renders and re-mounts the dragged item, so the detach of its handlers lands right when the finger is lifted. It happens every few drags on a real device.

Workaround we ship (patch-package), which removed the crash on the same device:

-    orchestrator!!.onHandlerStateChange(this, newState, oldState)
+    orchestrator?.onHandlerStateChange(this, newState, oldState)

A more targeted fix could be re-checking the handler inside the posted runnable (e.g. if (handler.view != null) handler.cancel()), or making cancel() a no-op on a handler that has been reset.

Steps to reproduce

  1. Render a Sortable.Grid from react-native-sortables 1.10.1 with customHandle, whose items contain components using GestureDetector (Gesture.Tap / Gesture.LongPress composed with Gesture.Exclusive).
  2. On Android, long press a Sortable.Handle and drag an item to a new position, then lift the finger.
  3. In onDragEnd, update the data so that the grid re-renders and the dropped item is re-mounted (its GestureDetectors are detached).
  4. Repeat steps 2-3: after a few drops the app crashes with the stack trace above.

It is a timing race, so it is intermittent: it reproduced on a real device (Sunmi D3 MINI, Android 13) every few drags and not on an Android emulator.

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

No standalone repository yet: it is a race and we could not make it deterministic outside the device. The analysis above points to the exact lines (RNGestureHandlerRegistry.kt:138 posting cancel(), GestureHandler.reset() clearing orchestrator, GestureHandler.kt:633 dereferencing it).

Gesture Handler version

3.3.0

React Native version

0.87.1

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

New Architecture (Fabric)

Build type

Release mode

Device

Real device

Device model

Sunmi D3 MINI (Android 13)

Related libraries

  • react-native-reanimated 4.7.1
  • react-native-worklets 0.13.0
  • react-native-screens 4.28.0
  • react-native-sortables 1.10.1

Acknowledgements

Yes

Activity

  1. github-actions commented on Oct 5, 2026

    @github-actions

    Hey @Davide-Michelotti! 👋

    Some of the required information in this issue is missing, incomplete or invalid. Could you take another look at the template and make sure each of these is filled in with something we can act on?

    • Description - what happens, and what you expected to happen instead
    • Steps to reproduce - steps somebody else can follow, not just numbering
    • Gesture Handler version - the exact version from your package.json or lockfile
    • React Native version - the exact version from your package.json or lockfile
    • Versions of related libraries you mention (Reanimated, Worklets, Screens, Expo) - the versions actually installed
    • Platforms - the platforms you actually see the bug on

    Once the issue is updated we will take another look. Thanks!

  2. github-actions commented on Oct 5, 2026

    @github-actions

    Hey @Davide-Michelotti! 👋

    The issue doesn't seem to contain a minimal reproduction.

    Could you provide a snack, a gist or a link to a GitHub repository that reproduces the problem?

  3. m-bert commented on Oct 9, 2026

    @m-bert
    Collaborator

    Hi @Davide-Michelotti! Could you please check whether #4584 fixes this issue?

  4. Davide-Michelotti commented on Oct 12, 2026

    @Davide-Michelotti
    Author

    Hi @m-bert, thanks for looking into this!

    I tested #4584 on the same device (Sunmi D3 MINI, Android 13, release build, New Architecture), now on React Native 0.88.0-rc.4 with Gesture Handler 3.3.0, Reanimated/Sortables unchanged.

    • 3.3.0 unpatched: still crashes with the same stack trace (NullPointerException at GestureHandler.moveToState(GestureHandler.kt:633) ← cancel ← RNGestureHandlerRegistry.detachHandlerInternal), dragging tiles by hand in our Sortable.Grid.
    • 3.3.0 + the diff from [Android] Fix NPE when a touch cancel callback finishes the handler mid-transition #4584: same gestures, no crash. Logcat shows 36 × Can't cancel already finished gesture… warnings in that session, so the cancel path was hit repeatedly and handled.

    So #4584 fixes the issue for us. Your explanation also matches our setup better than my original analysis: react-native-sortables calls control.fail() inside onTouchesCancelled.

    One note: I could not reproduce it with scripted single-pointer drags (adb shell input motionevent, ~160 drags on the unpatched build), only by hand.

    Feel free to close this once the fix is released. Thanks again!

  5. m-bert commented on Oct 13, 2026

    @m-bert
    Collaborator

    Thank you for confirmation! In that case I'm closing this issue, let me know if you need any further assistance 😅

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions