From 796f48aad09101e7b60973e2d7756fcf03274e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Fri, 9 Jan 2026 14:23:42 +0100 Subject: [PATCH 1/3] fixed android state manager --- .../gesturehandler/core/GestureHandler.kt | 26 +++++++++++++++++++ .../core/GestureHandlerOrchestrator.kt | 10 +++++++ .../react/RNGestureHandlerDetectorView.kt | 17 ++++++++++++ .../react/RNGestureHandlerRootHelper.kt | 4 +++ .../react/RNGestureHandlerRootView.kt | 5 ++++ 5 files changed, 62 insertions(+) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index 877d9d7126..c20a34e840 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -175,6 +175,26 @@ open class GestureHandler { interactionController = controller } + fun prepareForManual(orchestrator: GestureHandlerOrchestrator?) { + check(this.orchestrator == null) { + "Already prepared or hasn't been reset" + } + Arrays.fill(trackedPointerIDs, -1) + trackedPointersIDsCount = 0 + state = STATE_UNDETERMINED + this.orchestrator = orchestrator + + val content = getActivity(view?.context)?.findViewById(android.R.id.content) + if (content != null) { + content.getLocationOnScreen(windowOffset) + } else { + windowOffset[0] = 0 + windowOffset[1] = 0 + } + + onPrepare() + } + fun prepare(view: View?, orchestrator: GestureHandlerOrchestrator?) { check(!(this.view != null || this.orchestrator != null)) { "Already prepared or hasn't been reset" @@ -593,6 +613,12 @@ open class GestureHandler { // generated faster than they can be treated by JS thread eventCoalescingKey = nextEventCoalescingKey++ } + + if (orchestrator == null) { + // If the state is set manually, the handler may not have been fully recorded by the orchestrator. + hostDetectorView?.recordHandlerIfNotPresentForManual(this) + } + orchestrator!!.onHandlerStateChange(this, newState, oldState) onStateChange(newState, oldState) } diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt index 3f13f15b82..03c84e95b1 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt @@ -441,7 +441,17 @@ class GestureHandlerOrchestrator( activationIndex = this@GestureHandlerOrchestrator.activationIndex++ } } + fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { + if (gestureHandlers.contains(handler)) { + return + } + gestureHandlers.add(handler) + handler.isActive = false + handler.isAwaiting = false + handler.activationIndex = Int.MAX_VALUE + handler.prepareForManual(this) + } private fun recordHandlerIfNotPresent(handler: GestureHandler, view: View) { if (gestureHandlers.contains(handler)) { return diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt index c57dd8ad37..4cfa9f6776 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt @@ -2,6 +2,7 @@ package com.swmansion.gesturehandler.react import android.content.Context import android.view.View +import android.view.ViewParent import com.facebook.react.bridge.ReadableArray import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper @@ -202,7 +203,23 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) { child.value.clear() } } + fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { + findGestureHandlerRootView()?.recordHandlerIfNotPresentForManual(handler) + } + + private fun findGestureHandlerRootView(): RNGestureHandlerRootView? { + var parent: ViewParent? = this.parent + var gestureHandlerRootView: RNGestureHandlerRootView? = null + + while (parent != null) { + if (parent is RNGestureHandlerRootView) { + gestureHandlerRootView = parent + } + parent = parent.parent + } + return gestureHandlerRootView + } private fun ReadableArray.mapVirtualChildren(): List = List(size()) { i -> val child = getMap(i) ?: return@List null val handlerTags = child.getArray("handlerTags")?.toIntList().orEmpty() diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt index 131119ecb7..b3944335f9 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt @@ -141,6 +141,10 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: orchestrator?.activateNativeHandlersForView(view) } + fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { + orchestrator?.recordHandlerIfNotPresentForManual(handler) + } + companion object { private const val MIN_ALPHA_FOR_TOUCH = 0.1f private fun findRootViewTag(viewGroup: ViewGroup): ViewGroup { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt index f66bcd8387..b3a8c669a3 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt @@ -10,6 +10,7 @@ import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.common.ReactConstants import com.facebook.react.uimanager.RootView import com.facebook.react.views.view.ReactViewGroup +import com.swmansion.gesturehandler.core.GestureHandler class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { private var moduleId: Int = -1 @@ -39,6 +40,10 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { rootHelper?.tearDown() } + fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { + rootHelper?.recordHandlerIfNotPresentForManual(handler) + } + override fun dispatchTouchEvent(event: MotionEvent) = if (rootViewEnabled && rootHelper!!.dispatchTouchEvent(event)) { true } else { From 27b2aa0459bd71dcde780b171428cc466cba3bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 13 Jan 2026 08:22:32 +0100 Subject: [PATCH 2/3] clean up --- .../gesturehandler/core/GestureHandler.kt | 22 +------------------ .../core/GestureHandlerOrchestrator.kt | 12 +--------- .../react/RNGestureHandlerDetectorView.kt | 4 ++-- .../react/RNGestureHandlerRootHelper.kt | 4 ++-- .../react/RNGestureHandlerRootView.kt | 4 ++-- 5 files changed, 8 insertions(+), 38 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index c20a34e840..e50325736c 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -175,26 +175,6 @@ open class GestureHandler { interactionController = controller } - fun prepareForManual(orchestrator: GestureHandlerOrchestrator?) { - check(this.orchestrator == null) { - "Already prepared or hasn't been reset" - } - Arrays.fill(trackedPointerIDs, -1) - trackedPointersIDsCount = 0 - state = STATE_UNDETERMINED - this.orchestrator = orchestrator - - val content = getActivity(view?.context)?.findViewById(android.R.id.content) - if (content != null) { - content.getLocationOnScreen(windowOffset) - } else { - windowOffset[0] = 0 - windowOffset[1] = 0 - } - - onPrepare() - } - fun prepare(view: View?, orchestrator: GestureHandlerOrchestrator?) { check(!(this.view != null || this.orchestrator != null)) { "Already prepared or hasn't been reset" @@ -616,7 +596,7 @@ open class GestureHandler { if (orchestrator == null) { // If the state is set manually, the handler may not have been fully recorded by the orchestrator. - hostDetectorView?.recordHandlerIfNotPresentForManual(this) + hostDetectorView?.recordHandlerIfNotPresent(this) } orchestrator!!.onHandlerStateChange(this, newState, oldState) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt index 03c84e95b1..5907cd6ac1 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt @@ -441,18 +441,8 @@ class GestureHandlerOrchestrator( activationIndex = this@GestureHandlerOrchestrator.activationIndex++ } } - fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { - if (gestureHandlers.contains(handler)) { - return - } - gestureHandlers.add(handler) - handler.isActive = false - handler.isAwaiting = false - handler.activationIndex = Int.MAX_VALUE - handler.prepareForManual(this) - } - private fun recordHandlerIfNotPresent(handler: GestureHandler, view: View) { + fun recordHandlerIfNotPresent(handler: GestureHandler, view: View?) { if (gestureHandlers.contains(handler)) { return } diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt index 4cfa9f6776..4d2c98e461 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt @@ -203,8 +203,8 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) { child.value.clear() } } - fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { - findGestureHandlerRootView()?.recordHandlerIfNotPresentForManual(handler) + fun recordHandlerIfNotPresent(handler: GestureHandler) { + findGestureHandlerRootView()?.recordHandlerIfNotPresent(handler) } private fun findGestureHandlerRootView(): RNGestureHandlerRootView? { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt index b3944335f9..fb327172be 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt @@ -141,8 +141,8 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: orchestrator?.activateNativeHandlersForView(view) } - fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { - orchestrator?.recordHandlerIfNotPresentForManual(handler) + fun recordHandlerIfNotPresent(handler: GestureHandler) { + orchestrator?.recordHandlerIfNotPresent(handler, null) } companion object { diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt index b3a8c669a3..670ed5eba8 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt @@ -40,8 +40,8 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) { rootHelper?.tearDown() } - fun recordHandlerIfNotPresentForManual(handler: GestureHandler) { - rootHelper?.recordHandlerIfNotPresentForManual(handler) + fun recordHandlerIfNotPresent(handler: GestureHandler) { + rootHelper?.recordHandlerIfNotPresent(handler) } override fun dispatchTouchEvent(event: MotionEvent) = if (rootViewEnabled && rootHelper!!.dispatchTouchEvent(event)) { From f3c1e939d4933a11a32231a4faa76d0afae8830d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Antoni=20Kwa=C5=9Bniewski?= Date: Tue, 13 Jan 2026 08:45:03 +0100 Subject: [PATCH 3/3] add throw --- .../java/com/swmansion/gesturehandler/core/GestureHandler.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt index e50325736c..163d4d6793 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt @@ -594,6 +594,10 @@ open class GestureHandler { eventCoalescingKey = nextEventCoalescingKey++ } + check(hostDetectorView != null || orchestrator != null) { + "Manually handled gesture had not been assigned to any detector" + } + if (orchestrator == null) { // If the state is set manually, the handler may not have been fully recorded by the orchestrator. hostDetectorView?.recordHandlerIfNotPresent(this)