Skip to content

Conversation

@j-piasecki
Copy link
Member

Description

This PR changes the ordering of onBegin and onTouchesDown events on Android. onBegin was invoked first, as some gestures required additional setup when they started tracking the first pointer, which is happening after the touch events are dispatched. The workaround for that was to send onTouchesDown later but it resulted in inconsistent behavior between platforms.
The gesture initialization is now abstracted to the parent class and may be invoked by touch events. The one caveat is that it requires an instance of MotionEvent so one needs to be remembered when dispatching the touch event.

Fixes part of #2263.

Test plan

Tested on the example app and on the code from the issue.

@j-piasecki j-piasecki force-pushed the @jpiasecki/fix-event-order-android-2 branch from 89aea03 to d58e6cc Compare August 10, 2023 08:31
@j-piasecki j-piasecki closed this Oct 31, 2025
j-piasecki added a commit that referenced this pull request Nov 3, 2025
… too soon (#3793)

## Description

Supersedes
#2283

On Android, `onTouchesDown` was delayed to be dispatched after `onBegin`
so that the handlers had the time to initialize themselves (which
happens in `onHandle`) so that the non-touch events would have correct
data.

The old approach was saving the event, which triggered the touch event,
and using that event to initialize the handler during imperative state
change. The issues with that approach were:
1. Storing the event for the duration of the event handling
2. It wouldn't work with asynchronous state changes, which are now
possible

The new approach is to add an option to force gestures to initialize
regardless of the state they are in. This way, when the state is updated
and the current state of the handler is `UNDETERMINED`, the flag is set
on the handler, and when it starts handling the event, it will first
initialize itself.

## Test plan

Tested on the following snippet, with affected gestures
```
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { usePan, NativeDetector } from 'react-native-gesture-handler';

export default function EmptyExample() {
  const pan = usePan({
    onTouchesDown: () => {
      'worklet';
      console.log('Touch down');
      globalThis._setGestureStateSync(57, 4);
    },
    onTouchesMove: () => {
      'worklet';
      console.log('Touch move');
    },
    onTouchesUp: () => {
      'worklet';
      console.log('Touch up');
    },
    onTouchesCancelled: () => {
      'worklet';
      console.log('Touch cancel');
    },
    onBegin: () => {
      'worklet';
      console.log('Gesture begin');
    },
    onStart: (e) => {
      'worklet';
      console.log('Gesture start');
    },
    onUpdate: (e) => {
      'worklet';
      console.log('Gesture update', e.handlerData.translationX, e.handlerData.translationY);
    },
    onEnd: () => {
      'worklet';
      console.log('Gesture end');
    },
    onFinalize: () => {
      'worklet';
      console.log('Gesture finalize');
    },
  });

  console.log('Rendering EmptyExamplee', pan.tag);

  return (
    <View style={styles.container}>
      <NativeDetector gesture={pan}>
        <View style={{ width: 300, height: 300, backgroundColor: 'green' }} />
      </NativeDetector>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

```
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.

3 participants