Skip to content

Commit 68d099a

Browse files
authored
fix(Android): pressables lose focus on fast movement when gesture-handler is present (#2819)
## Description When `GestureHandler` & `GestureHandlerRootView` are present, the pressables in subtree of `GestureHandler` will lose focus on fast initial movement. See the video below 👇. > [!note] > Edit: the *fast movement* requirement most likely comes from the fact that we use `fling` for the `EmptyGestureHandler`. In case you use e.g. `Pan`, the movement does not have to fast. > [!note] > The issue is reproducible **w/o** `react-native-screens`. The issue affects us, because #1913 added custom-screen-transitions based on integration with `gesture-handler` & `reanimated`. Each `ScreenStack` is since then wrapped in a `GestureDetector`, which uses either [`EmptyGestureHandler`](https://github.com/software-mansion/react-native-screens/blob/4d044dab8c387c6f3988434276594a2d917f5022/src/gesture-handler/ScreenGestureDetector.tsx#L27) or [one defined based on gesture config](https://github.com/software-mansion/react-native-screens/blob/4d044dab8c387c6f3988434276594a2d917f5022/src/gesture-handler/ScreenGestureDetector.tsx#L247). ### Issue mechanism Basically it seems like bug in `gesture-handler`. The `RNGestureHandlerRootHelper` [cancels gesture](https://github.com/software-mansion/react-native-gesture-handler/blob/4232fc3127949c2869769542bfc7c3710b1348ee/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt#L87) on the root view, causing RN pressable gesture handling to be cancelled. Instead, the attached gesture-handler intercepts the touch events. > [!warning] > I was able to reproduce the issue 100% reliably earlier w/o presence of the `Screen` component, however > testing it now it seems that the `Screen` is required (w/o `Screen` in view hierarchy it seems to work fine...). > Dunno what is going on 🤷 ### Issue recording https://github.com/user-attachments/assets/d0e9de2e-7d77-4379-b3f8-5da9e8b028ac ## Changes It looks to me that it has to be handled on the side of `gesture-handler` / `reac-native` core. Currently, I've decided to `disable` the `EmptyGestureHandler` we wrap the `ScreenStack`. > [!caution] > **This is only partial workaround**. There still will be issue in case someone setups the `goBackGesture` & we pass `enabled` `GestureHandler` to `GestureDetector`. ## Test code and steps to reproduce `Test2819`, may require you to wrap `HomeOne` inside a `Screen`, depending on the device/RN/other code mood I guess. ## Checklist - [x] Included code example that can be used to test this change - [ ] Ensured that CI passes
1 parent ef3cc02 commit 68d099a

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

apps/src/tests/Test2819.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
import { Text, View } from 'react-native';
3+
import PressableWithFeedback from '../shared/PressableWithFeedback';
4+
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
5+
6+
7+
function SharedPressable() {
8+
return (
9+
<PressableWithFeedback>
10+
<View style={{ height: 120 }}>
11+
<Text>Regular pressable</Text>
12+
</View>
13+
</PressableWithFeedback>
14+
);
15+
}
16+
17+
function HomeOne() {
18+
return (
19+
<View style={{ height: 600, backgroundColor: 'seagreen' }}>
20+
<SharedPressable />
21+
</View>
22+
);
23+
}
24+
25+
export function App() {
26+
const gesture = Gesture.Pan()
27+
.onBegin(() => {
28+
'worklet';
29+
})
30+
.enabled(true); // Change this to `false` to fix the issue.
31+
return (
32+
<GestureHandlerRootView style={{ flex: 1 }}>
33+
<GestureDetector gesture={gesture}>
34+
<HomeOne />
35+
</GestureDetector>
36+
</GestureHandlerRootView>
37+
);
38+
}
39+
40+
export default App;

apps/src/tests/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export { default as Test2717 } from './Test2717';
126126
export { default as Test2767 } from './Test2767';
127127
export { default as Test2789 } from './Test2789';
128128
export { default as Test2811 } from './Test2811';
129+
export { default as Test2819 } from './Test2819';
129130
export { default as TestScreenAnimation } from './TestScreenAnimation';
130131
export { default as TestScreenAnimationV5 } from './TestScreenAnimationV5';
131132
export { default as TestHeader } from './TestHeader';

src/gesture-handler/ScreenGestureDetector.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import {
2424
} from './constraints';
2525
import { GestureProviderProps } from '../types';
2626

27-
const EmptyGestureHandler = Gesture.Fling();
27+
// The detector is disabled to work around issue with pressables
28+
// losing focus. See https://github.com/software-mansion/react-native-screens/pull/2819
29+
const EmptyGestureHandler = Gesture.Fling().enabled(false);
2830

2931
const ScreenGestureDetector = ({
3032
children,

0 commit comments

Comments
 (0)