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 3d371e0076..03bb1cfd8b 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 @@ -29,7 +29,16 @@ class GestureHandlerOrchestrator( var minimumAlphaForTraversal = DEFAULT_MIN_ALPHA_FOR_TRAVERSAL private val gestureHandlers = arrayListOf() private val awaitingHandlers = arrayListOf() - private val preparedHandlers = arrayListOf() + + // Pool of reusable lists for snapshotting `gestureHandlers` during event delivery. + private val handlerListPool = ArrayDeque>() + + private fun obtainHandlerList() = handlerListPool.pollLast() ?: ArrayList() + + private fun recycleHandlerList(list: ArrayList) { + list.clear() + handlerListPool.addLast(list) + } // In `onHandlerStateChange` method we iterate through `awaitingHandlers`, but calling `tryActivate` may modify this list. // To avoid `ConcurrentModificationException` we iterate through copy. There is one more problem though - if handler was @@ -260,20 +269,24 @@ class GestureHandlerOrchestrator( } private fun deliverEventToGestureHandlers(event: MotionEvent) { - // Copy handlers to "prepared handlers" array, because the list of active handlers can change - // as a result of state updates - preparedHandlers.clear() - preparedHandlers.addAll(gestureHandlers) + // Snapshot handlers into a pooled list, because the list of active handlers can change + // as a result of state updates (and delivery can be re-entrant). + val handlersToProcess = obtainHandlerList() + handlersToProcess.addAll(gestureHandlers) // We want to deliver events to active handlers first in order of their activation (handlers // that activated first will first get event delivered). Otherwise we deliver events in the // order in which handlers has been added ("most direct" children goes first). Therefore we rely // on Arrays.sort providing a stable sort (as children are registered in order in which they // should be tested) - preparedHandlers.sortWith(handlersComparator) + handlersToProcess.sortWith(handlersComparator) - for (handler in preparedHandlers) { - deliverEventToGestureHandler(handler, event) + try { + for (handler in handlersToProcess) { + deliverEventToGestureHandler(handler, event) + } + } finally { + recycleHandlerList(handlersToProcess) } } @@ -284,12 +297,9 @@ class GestureHandlerOrchestrator( handler.cancel() } - // Copy handlers to "prepared handlers" array, because the list of active handlers can change + // Iterate over a copy, because the list of active handlers can change // as a result of state updates - preparedHandlers.clear() - preparedHandlers.addAll(gestureHandlers) - - for (handler in gestureHandlers.asReversed()) { + for (handler in gestureHandlers.reversed()) { handler.cancel() } }