-
-
Notifications
You must be signed in to change notification settings - Fork 980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove Gesture Handler limits on Android #2672
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me (although I haven't tested it)
android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
Outdated
Show resolved
Hide resolved
android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
Outdated
Show resolved
Hide resolved
android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
Outdated
Show resolved
Hide resolved
android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
Outdated
Show resolved
Hide resolved
android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt
Outdated
Show resolved
Hide resolved
private val gestureHandlers = arrayListOf<GestureHandler<*>?>() | ||
private val awaitingHandlers = arrayListOf<GestureHandler<*>?>() | ||
private val preparedHandlers = arrayListOf<GestureHandler<*>?>() | ||
private val handlersToCancel = arrayListOf<GestureHandler<*>?>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point do we need to allow null
values? It was nullable because the size of the array was constant to allow for nothing is here
value. If we're going to use ArrayList
we can simply remove things that shouldn't be there, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I left null
possibility because of how removing handler worked before last commit, but now I think it should be fine (6957bb1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I would be grateful if you could check if that approach is correct (line 74)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean, there's a closing bracket on line 74 😅
if (isFinished(handler.state) && !handler.isAwaiting) { | ||
gestureHandlers[i] = null | ||
shouldCleanEmptyCells = true | ||
gestureHandlers.remove(handler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we modify the collection while iterating over it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure that calling Collection.reversed()
returns new collection - in that case it should be fine (see docs for context)
private val gestureHandlers = arrayListOf<GestureHandler<*>?>() | ||
private val awaitingHandlers = arrayListOf<GestureHandler<*>?>() | ||
private val preparedHandlers = arrayListOf<GestureHandler<*>?>() | ||
private val handlersToCancel = arrayListOf<GestureHandler<*>?>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean, there's a closing bracket on line 74 😅
val otherHandler = gestureHandlers[i]!! | ||
for (otherHandler in gestureHandlers) { | ||
if (shouldHandlerBeCancelledBy(otherHandler, handler)) { | ||
handlersToCancel[toCancelCount++] = otherHandler | ||
handlersToCancel.add(otherHandler) | ||
} | ||
} | ||
for (i in toCancelCount - 1 downTo 0) { | ||
handlersToCancel[i]!!.cancel() | ||
|
||
for (otherHandler in handlersToCancel.reversed()) { | ||
otherHandler.cancel() | ||
} | ||
|
||
handlersToCancel.clear() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need handlersToCancel
at all here? I think you can iterate over gestureHandlers.asReversed()
and just cancel relevant handlers inside the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree - done in da2c421
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
@@ -160,19 +159,12 @@ class GestureHandlerOrchestrator( | |||
activationIndex = [email protected]++ | |||
} | |||
|
|||
// Cancel all handlers that are required to be cancel upon current handler's activation | |||
for (otherHandler in gestureHandlers) { | |||
for (otherHandler in gestureHandlers.reversed()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (otherHandler in gestureHandlers.reversed()) { | |
for (otherHandler in gestureHandlers.asReversed()) { |
I don't think the list is being mutated here, so we shouldn't need a copy.
…4711) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [react-native-gesture-handler](https://togithub.com/software-mansion/react-native-gesture-handler) | [`^2.13.4` -> `^2.14.0`](https://renovatebot.com/diffs/npm/react-native-gesture-handler/2.13.4/2.14.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react-native-gesture-handler/2.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-native-gesture-handler/2.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-native-gesture-handler/2.13.4/2.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-native-gesture-handler/2.13.4/2.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes <details> <summary>software-mansion/react-native-gesture-handler (react-native-gesture-handler)</summary> ### [`v2.14.0`](https://togithub.com/software-mansion/react-native-gesture-handler/releases/tag/2.14.0) [Compare Source](https://togithub.com/software-mansion/react-native-gesture-handler/compare/2.13.4...2.14.0) #### ❗ Important changes - Remove Gesture Handler limits on Android by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2672](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2672) - Add a new gesture relation - `blocksHandlers` - working like `reversed waitFor` by [@​j-piasecki](https://togithub.com/j-piasecki) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2627](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2627) - Support React Native 0.73 by [@​j-piasecki](https://togithub.com/j-piasecki) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2671](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2671) #### 🐛 Bug fixes - Fix double `onPress` by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2657](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2657) - Fix tvOS build by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2654](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2654) - Add a non-null assertion for `reactApplicationContext.javaScriptContextHolder` by [@​UNIDY2002](https://togithub.com/UNIDY2002) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2662](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2662) - Move REACT_NATIVE_VERSION to native-only code by [@​chriscoomber](https://togithub.com/chriscoomber) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2666](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2666) - Fix selection when TalkBack is enabled by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2669](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2669) - Fix inputs focus on safari by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2675](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2675) #### 🔢 Miscellaneous - Bump browserify-sign from 4.2.1 to 4.2.2 in /example by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2661](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2661) - Change Alert to console.log in new docs by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2670](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2670) - Remove `handlersToCancel` on web. by [@​m-bert](https://togithub.com/m-bert) in [https://github.com/software-mansion/react-native-gesture-handler/pull/2679](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2679) #### New Contributors - [@​chriscoomber](https://togithub.com/chriscoomber) made their first contribution in [https://github.com/software-mansion/react-native-gesture-handler/pull/2666](https://togithub.com/software-mansion/react-native-gesture-handler/pull/2666) **Full Changelog**: software-mansion/react-native-gesture-handler@2.13.4...2.14.0 </details> --- ### Configuration 📅 **Schedule**: Branch creation - "after 5pm,every weekend" in timezone America/Los_Angeles, Automerge - "after 5pm,every weekend" in timezone America/Los_Angeles. 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/valora-inc/wallet). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xMjEuMCIsInVwZGF0ZWRJblZlciI6IjM3LjEyMS4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: valora-bot <[email protected]>
## Description A while ago we decided to remove limit of gesture handlers on android (#2672). We missed one thing - `awaitngHandlers` can be modified while iterating over its items (inside [this loop](https://github.com/software-mansion/react-native-gesture-handler/blob/2e5df1c9a8595929b9879dc1246a7fd78de0549a/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt#L105)). This resulted in `ConcurrentModificationException`. This PR changes logic so that now we iterate over copy of `awaitingHandlers`, avoiding modification of collection that we iterate through. Even though it looks like it changes a lot, the only difference beside adding copy is changing ```jsx for(otherHandler in currentlyAwaitingHandlers){ if (shouldHandlerWaitForOther(otherHandler, handler)) { ... } } ``` into ```jsx for(otherHandler in currentlyAwaitingHandlers){ if (!shouldHandlerWaitForOther(otherHandler, handler)) { continue } ... } ``` to remove one level of nested `ifs`. ## Test plan <details> <summary> Modified code from #2739 </summary> ```jsx import React from 'react'; import { ScrollView, StyleSheet, Text, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated from 'react-native-reanimated'; const Showcase = () => { const [lastActions, setLastActions] = React.useState([]); const addAction = (action) => { setLastActions((actions) => { const date = new Date(); const time = date.toLocaleTimeString(); if (actions.length >= 3) { actions.pop(); } return [action + ': ' + time, ...actions]; }); }; const longPressGesture = Gesture.LongPress() .onStart(() => { addAction('long press'); }) .runOnJS(true); const doubleTapGesture = Gesture.Tap() .numberOfTaps(2) .onStart(() => { addAction('double tap'); }) .runOnJS(true); const gesture = Gesture.Race(doubleTapGesture, longPressGesture); return ( <View style={{ flex: 1, backgroundColor: 'white', marginHorizontal: 20, marginVertical: 60, }}> <ScrollView contentContainerStyle={{ gap: 20 }}> <GestureDetector gesture={gesture}> <Animated.View style={styles.outer}> <Blue onAddAction={addAction} /> </Animated.View> </GestureDetector> <View> {lastActions.map((action, index) => ( <Text key={index}> {index} - {action} </Text> ))} </View> <View style={{ gap: 20 }}> <Text> <Bold>DoubleTap</Bold>: on all rectangles - should print "double tap" - no tap gesture should be recognized ✅ </Text> <Text> <Bold>LongPress</Bold>: on all rectangles - should print "long press" - no tap gesture should be recognized ✅ </Text> <Text> <Bold>Tap</Bold>: on red - should do nothing ✅ </Text> <Text> <Bold>Tap</Bold>: on blue - should print "Blue: tap" ✅ </Text> <Text> <Bold>Tap</Bold>: on orange - should print "Orange: tap" ⛔️ - Crashes on Android </Text> </View> </ScrollView> </View> ); }; export default Showcase; const Blue = ({ onAddAction }) => { const tapGesture = Gesture.Tap() .onStart(() => { onAddAction('Blue: tap'); }) .runOnJS(true); const doubleTapGesture = Gesture.Tap() .numberOfTaps(2) .onStart(() => { onAddAction('double tap'); }) .runOnJS(true); const composedGesture = Gesture.Exclusive(doubleTapGesture, tapGesture); return ( <GestureDetector gesture={composedGesture}> <Animated.View style={styles.blue}> <Orange onAddAction={onAddAction} /> </Animated.View> </GestureDetector> ); }; const Orange = ({ onAddAction }) => { const tapGesture = Gesture.Tap() .onStart(() => { onAddAction('Orange: tap'); }) .runOnJS(true); const doubleTapGesture = Gesture.Tap() .numberOfTaps(2) .onStart(() => { onAddAction('double tap'); }) .runOnJS(true); return ( <GestureDetector gesture={Gesture.Exclusive(doubleTapGesture, tapGesture)}> <Animated.View style={styles.orange}></Animated.View> </GestureDetector> ); }; const Bold = ({ children }) => { return <Text style={{ fontWeight: 'bold' }}>{children}</Text>; }; const styles = StyleSheet.create({ outer: { padding: 20, backgroundColor: 'red', width: 300, height: 200, }, blue: { width: '80%', height: '80%', backgroundColor: 'blue', flexDirection: 'row', flexWrap: 'wrap', }, orange: { margin: 5, width: '50%', height: '50%', backgroundColor: 'orange', }, }); ``` </details> <details> <summary> Smaller example </summary> ```jsx import React from 'react'; import { StyleSheet, View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; export default function App() { const t3 = Gesture.Tap() .numberOfTaps(3) .onEnd(() => console.log('t3')); const t1_1 = Gesture.Tap().onEnd(() => console.log('t1_1')); const t1_2 = Gesture.Tap().onEnd(() => console.log('t1_2')); const g = Gesture.Exclusive(t3, t1_1, t1_2); return ( <GestureDetector gesture={g}> <View style={styles.box} /> </GestureDetector> ); } const styles = StyleSheet.create({ box: { width: 250, height: 250, backgroundColor: 'crimson', }, }); ``` </details>
Description
While looking at PRs I've stumbled upon this one. Turns out that
SIMULTANEOUS_GESTURE_HANDLER_LIMIT
does not refer to simultaneous gestures, but to all gesture handlers present in app. The upper limit shouldn't exist - this PR aims to remove it.Test plan
Tested on example app.