Skip to content

Conversation

@akwasniewski
Copy link
Contributor

@akwasniewski akwasniewski commented Jan 9, 2026

Description

The new StateManager is global and given handlerTag it can manually set the states of an arbitrary gesture. This causes errors, when the gesture, which state is being set, has not been yet recorded in the orchestrator. Recording gestures in the orchestrator on android is done lazily, thus if it never received touches it is not recorded.

Test plan

Tested on the following example

Details
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { GestureHandlerRootView, GestureDetector, useLongPressGesture, GestureStateManager, usePanGesture, useSimultaneousGestures, useTapGesture } from 'react-native-gesture-handler';

export default function TwoPressables() {
  const longPress = useLongPressGesture({
    onTouchesDown: (e) => {
      'worklet';
      console.log("touches down")
    },
    onActivate: () => {
      'worklet';
      console.log("long pressed")
    },
    minDuration: 100000000,
    disableReanimated: true
  })
  const pan = useTapGesture({
    onTouchesDown: () => {
      'worklet';
      console.log("tap")

      GestureStateManager.activate(longPress.tag)
    },
    disableReanimated: true
  });
  return (
    <GestureHandlerRootView>
      <View style={styles.root}>
        <GestureDetector gesture={longPress}>
          <View style={styles.outer}>
            <Text style={styles.label}>Long Press</Text>
          </View>
        </GestureDetector>
        <GestureDetector gesture={pan}>
          <View style={styles.outer}>
            <Text style={styles.label}>Pan</Text>
          </View>
        </GestureDetector>

      </View>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f7f7f7',
  },
  outer: {
    padding: 20,
    backgroundColor: '#ddd',
    borderRadius: 12,
    marginBottom: 50
  },
  label: {
    fontSize: 18,
    marginBottom: 10,
  },
})

@akwasniewski akwasniewski requested a review from m-bert January 9, 2026 13:32
@akwasniewski akwasniewski changed the base branch from main to next January 9, 2026 13:32
Copy link
Contributor

@m-bert m-bert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said in the first comment, I'm not a big fan of ForManual suffix. I think we could either use overloading (where possible) or add more descriptive suffix.

}

fun prepareForManual(orchestrator: GestureHandlerOrchestrator?) {
check(this.orchestrator == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be this.orchestrator != null? Especially that the condition that starts all this process is if (orchestrator == null).

Also, the only way to end up here is from orchestrator itself, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check works differently than if, this throws the provided message when the orchestrator was already set.

interactionController = controller
}

fun prepareForManual(orchestrator: GestureHandlerOrchestrator?) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this name. First of all, if it has the same purpose we could overload prepare (maybe with little comment when it is useful). If we want to keep it different, then I guess it should be more descriptive, like prepareForManualStateChange, prepareForManualManagement, or some other form.

Also, it seems to share most of the logic with prepare - we could extract it into separate function if needed so that we don't have this redundancy.

Copy link
Contributor Author

@akwasniewski akwasniewski Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up in 27b2aa0. prepare works fine if we pass null as view, so we can just do that.

Comment on lines 444 to 454
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, most of the logic is repeated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

Comment on lines 617 to 620
if (orchestrator == null) {
// If the state is set manually, the handler may not have been fully recorded by the orchestrator.
hostDetectorView?.recordHandlerIfNotPresentForManual(this)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should throw error when both, orchestrator and hostDetectorView are null. This may happen if gesture is defined, but is not assigned to any of the detectors. In that case I believe it will still crash, but with other, less descriptive error (probably NullPointerException).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, f3c1e93, I will check whether this works on other platforms

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On web the error is more fitting, Expected delegate view to be HTMLElement and on iOS it fails silently

@akwasniewski akwasniewski requested a review from m-bert January 14, 2026 09:14
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