diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index c00a1ae503..a5819f3c84 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -61,7 +61,7 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : } @ReactMethod - override fun createGestureHandler(handlerName: String, handlerTagDouble: Double, config: ReadableMap) { + override fun createGestureHandler(handlerName: String, handlerTagDouble: Double, config: ReadableMap): Boolean { if (ReanimatedProxy.REANIMATED_INSTALLED && !uiRuntimeDecorated) { uiRuntimeDecorated = decorateUIRuntime() } @@ -69,6 +69,8 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : val handlerTag = handlerTagDouble.toInt() createGestureHandlerHelper(handlerName, handlerTag, config) + + return true } @ReactMethod diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index 1e520a20e3..64345c5364 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -119,7 +119,7 @@ - (bool)installUIRuntimeBindings }); } -- (void)createGestureHandler:(NSString *)handlerName handlerTag:(double)handlerTag config:(NSDictionary *)config +- (NSNumber *)createGestureHandler:(NSString *)handlerName handlerTag:(double)handlerTag config:(NSDictionary *)config { if (!_checkedIfReanimatedIsAvailable) { _isReanimatedAvailable = [self.moduleRegistry moduleForName:"ReanimatedModule"] != nil; @@ -129,9 +129,10 @@ - (void)createGestureHandler:(NSString *)handlerName handlerTag:(double)handlerT _uiRuntimeDecorated = [self installUIRuntimeBindings]; } - [self addOperationBlock:^(RNGestureHandlerManager *manager) { - [manager createGestureHandler:handlerName tag:[NSNumber numberWithDouble:handlerTag] config:config]; - }]; + RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId]; + [manager createGestureHandler:handlerName tag:[NSNumber numberWithDouble:handlerTag] config:config]; + + return @1; } - (void)attachGestureHandler:(double)handlerTag newView:(double)viewTag actionType:(double)actionType diff --git a/packages/react-native-gesture-handler/src/handlers/utils.ts b/packages/react-native-gesture-handler/src/handlers/utils.ts index 7dc756aa02..0d07a32289 100644 --- a/packages/react-native-gesture-handler/src/handlers/utils.ts +++ b/packages/react-native-gesture-handler/src/handlers/utils.ts @@ -62,15 +62,26 @@ export function findNodeHandle( } return findNodeHandleRN(node) ?? null; } + +let scheduledOperations: (() => void)[] = []; let flushOperationsScheduled = false; export function scheduleFlushOperations() { if (!flushOperationsScheduled) { flushOperationsScheduled = true; ghQueueMicrotask(() => { + for (const operation of scheduledOperations) { + operation(); + } + scheduledOperations = []; RNGestureHandlerModule.flushOperations(); flushOperationsScheduled = false; }); } } + +export function scheduleOperationToBeFlushed(operation: () => void) { + scheduledOperations.push(operation); + scheduleFlushOperations(); +} diff --git a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts index e1f62994e3..c1dcd6a4e7 100644 --- a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts +++ b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts @@ -10,7 +10,7 @@ export interface Spec extends TurboModule { // Record<> is not supported by codegen // eslint-disable-next-line @typescript-eslint/ban-types config: Object - ) => void; + ) => boolean; attachGestureHandler: ( handlerTag: Double, newView: Double, diff --git a/packages/react-native-gesture-handler/src/v3/NativeProxy.ts b/packages/react-native-gesture-handler/src/v3/NativeProxy.ts new file mode 100644 index 0000000000..14431b433b --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeProxy.ts @@ -0,0 +1,54 @@ +import { scheduleOperationToBeFlushed } from '../handlers/utils'; +import RNGestureHandlerModule from '../RNGestureHandlerModule'; +import { + BaseGestureConfig, + GestureRelations, + SingleGestureName, +} from './types'; + +// Destructure functions that can be called on the UI thread to have +// a raw HostFunction reference +const { flushOperations, updateGestureHandlerConfig } = RNGestureHandlerModule; + +export const NativeProxy = { + createGestureHandler: >( + handlerName: SingleGestureName, + handlerTag: number, + config?: T + ) => { + RNGestureHandlerModule.createGestureHandler( + handlerName, + handlerTag, + config || {} + ); + }, + setGestureHandlerConfig: ( + handlerTag: number, + newConfig: BaseGestureConfig + ) => { + scheduleOperationToBeFlushed(() => { + RNGestureHandlerModule.setGestureHandlerConfig(handlerTag, newConfig); + }); + }, + // updateGestureHandlerConfig can be called on the UI thread when using + // SharedValue binding. Therefore, it needs to be a worklet and we flush + // immediately since we're likely already on the UI thread. + updateGestureHandlerConfig: ( + handlerTag: number, + newConfig: BaseGestureConfig + ) => { + 'worklet'; + updateGestureHandlerConfig(handlerTag, newConfig); + flushOperations(); + }, + dropGestureHandler: (handlerTag: number) => { + scheduleOperationToBeFlushed(() => { + RNGestureHandlerModule.dropGestureHandler(handlerTag); + }); + }, + configureRelations: (handlerTag: number, relations: GestureRelations) => { + scheduleOperationToBeFlushed(() => { + RNGestureHandlerModule.configureRelations(handlerTag, relations); + }); + }, +} as const; diff --git a/packages/react-native-gesture-handler/src/v3/NativeProxy.web.ts b/packages/react-native-gesture-handler/src/v3/NativeProxy.web.ts new file mode 100644 index 0000000000..abafb2ed5d --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeProxy.web.ts @@ -0,0 +1,38 @@ +import RNGestureHandlerModule from '../RNGestureHandlerModule'; +import { + BaseGestureConfig, + GestureRelations, + SingleGestureName, +} from './types'; + +export const NativeProxy = { + createGestureHandler: >( + handlerName: SingleGestureName, + handlerTag: number, + config?: T + ) => { + RNGestureHandlerModule.createGestureHandler( + handlerName, + handlerTag, + config || {} + ); + }, + setGestureHandlerConfig: ( + handlerTag: number, + newConfig: BaseGestureConfig + ) => { + RNGestureHandlerModule.setGestureHandlerConfig(handlerTag, newConfig); + }, + updateGestureHandlerConfig: ( + handlerTag: number, + newConfig: BaseGestureConfig + ) => { + RNGestureHandlerModule.updateGestureHandlerConfig(handlerTag, newConfig); + }, + dropGestureHandler: (handlerTag: number) => { + RNGestureHandlerModule.dropGestureHandler(handlerTag); + }, + configureRelations: (handlerTag: number, relations: GestureRelations) => { + RNGestureHandlerModule.configureRelations(handlerTag, relations); + }, +} as const; diff --git a/packages/react-native-gesture-handler/src/v3/detectors/utils.ts b/packages/react-native-gesture-handler/src/v3/detectors/utils.ts index e33d896a2c..2c8f51a126 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/detectors/utils.ts @@ -4,13 +4,12 @@ // For `waitFor` we need array as order of the gestures matters. // For `simultaneousHandlers` we use Set as the order doesn't matter. -import { scheduleFlushOperations } from '../../handlers/utils'; -import RNGestureHandlerModule from '../../RNGestureHandlerModule'; import { tagMessage } from '../../utils'; import { isComposedGesture, prepareRelations, } from '../hooks/utils/relationUtils'; +import { NativeProxy } from '../NativeProxy'; import { ComposedGestureName, Gesture } from '../types'; // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. @@ -27,7 +26,7 @@ export const traverseAndConfigureRelations = ( node.gestureRelations.simultaneousHandlers.push(...simultaneousHandlers); node.gestureRelations.waitFor.push(...waitFor); - RNGestureHandlerModule.configureRelations(node.tag, { + NativeProxy.configureRelations(node.tag, { waitFor: node.gestureRelations.waitFor, simultaneousHandlers: node.gestureRelations.simultaneousHandlers, blocksHandlers: node.gestureRelations.blocksHandlers, @@ -143,13 +142,8 @@ export function configureRelations( traverseAndConfigureRelations(gesture, simultaneousHandlers); } else { - RNGestureHandlerModule.configureRelations( - gesture.tag, - gesture.gestureRelations - ); + NativeProxy.configureRelations(gesture.tag, gesture.gestureRelations); } - - scheduleFlushOperations(); } export function ensureNativeDetectorComponent( diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 9e48cffc38..714f000333 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -1,6 +1,5 @@ import { useEffect, useMemo, useRef } from 'react'; import { getNextHandlerTag } from '../../handlers/getNextHandlerTag'; -import RNGestureHandlerModule from '../../RNGestureHandlerModule'; import { useGestureCallbacks } from './useGestureCallbacks'; import { prepareConfig, @@ -11,7 +10,7 @@ import { } from './utils'; import { tagMessage } from '../../utils'; import { BaseGestureConfig, SingleGesture, SingleGestureName } from '../types'; -import { scheduleFlushOperations } from '../../handlers/utils'; +import { NativeProxy } from '../NativeProxy'; export function useGesture( type: SingleGestureName, @@ -81,22 +80,18 @@ export function useGesture( currentGestureRef.current.type !== (type as string) ) { currentGestureRef.current = { type, tag }; - RNGestureHandlerModule.createGestureHandler(type, tag, {}); - // It's possible that this can cause errors about handler not being created when attempting to mount NativeDetector - scheduleFlushOperations(); + NativeProxy.createGestureHandler(type, tag, {}); } useEffect(() => { return () => { - RNGestureHandlerModule.dropGestureHandler(tag); - scheduleFlushOperations(); + NativeProxy.dropGestureHandler(tag); }; }, [type, tag]); useEffect(() => { const preparedConfig = prepareConfigForNativeSide(type, config); - RNGestureHandlerModule.setGestureHandlerConfig(tag, preparedConfig); - scheduleFlushOperations(); + NativeProxy.setGestureHandlerConfig(tag, preparedConfig); bindSharedValues(config, tag); diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts index 87441a1b68..5af8f55745 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts @@ -1,4 +1,4 @@ -import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; +import { NativeProxy } from '../../NativeProxy'; import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper'; import { BaseGestureConfig, @@ -23,8 +23,8 @@ function hash(str: string) { const SHARED_VALUE_OFFSET = 1.618; -// This is used to obtain HostFunction that can be executed on the UI thread -const { updateGestureHandlerConfig, flushOperations } = RNGestureHandlerModule; +// Don't transfer entire NativeProxy to the UI thread +const { updateGestureHandlerConfig } = NativeProxy; export function bindSharedValues( config: BaseGestureConfig, @@ -52,7 +52,6 @@ export function bindSharedValues( } else { updateGestureHandlerConfig(handlerTag, { [configKey]: value }); } - flushOperations(); }); };