Skip to content
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

Merged
merged 6 commits into from
Nov 20, 2023
Merged

Conversation

m-bert
Copy link
Contributor

@m-bert m-bert commented Nov 9, 2023

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.

Copy link
Member

@tomekzaw tomekzaw left a 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)

@m-bert m-bert marked this pull request as ready for review November 9, 2023 14:54
Comment on lines 23 to 26
private val gestureHandlers = arrayListOf<GestureHandler<*>?>()
private val awaitingHandlers = arrayListOf<GestureHandler<*>?>()
private val preparedHandlers = arrayListOf<GestureHandler<*>?>()
private val handlersToCancel = arrayListOf<GestureHandler<*>?>()
Copy link
Member

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?

Copy link
Contributor Author

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)

Copy link
Contributor Author

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)

Copy link
Member

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)
Copy link
Member

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?

Copy link
Contributor Author

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)

Comment on lines 23 to 26
private val gestureHandlers = arrayListOf<GestureHandler<*>?>()
private val awaitingHandlers = arrayListOf<GestureHandler<*>?>()
private val preparedHandlers = arrayListOf<GestureHandler<*>?>()
private val handlersToCancel = arrayListOf<GestureHandler<*>?>()
Copy link
Member

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 😅

Comment on lines 188 to 174
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()
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

@j-piasecki j-piasecki left a 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 = this@GestureHandlerOrchestrator.activationIndex++
}

// Cancel all handlers that are required to be cancel upon current handler's activation
for (otherHandler in gestureHandlers) {
for (otherHandler in gestureHandlers.reversed()) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

@m-bert m-bert merged commit 25ba4b8 into main Nov 20, 2023
3 checks passed
@m-bert m-bert deleted the @mbert/remove-simultaneous-limit branch November 20, 2023 07:15
m-bert added a commit that referenced this pull request Nov 21, 2023
## Description

While working on removing [gesture handlers limit on android](#2672) I've though that we can also remove `handlersToCancel` array on web.

## Test plan

Tested on example app.
renovate bot referenced this pull request in valora-inc/wallet Jan 7, 2024
…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://github.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://github.com/software-mansion/react-native-gesture-handler/releases/tag/2.14.0)

[Compare
Source](https://github.com/software-mansion/react-native-gesture-handler/compare/2.13.4...2.14.0)

#### ❗ Important changes

- Remove Gesture Handler limits on Android by
[@&#8203;m-bert](https://github.com/m-bert) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2672](https://github.com/software-mansion/react-native-gesture-handler/pull/2672)
- Add a new gesture relation - `blocksHandlers` - working like `reversed
waitFor` by [@&#8203;j-piasecki](https://github.com/j-piasecki) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2627](https://github.com/software-mansion/react-native-gesture-handler/pull/2627)
- Support React Native 0.73 by
[@&#8203;j-piasecki](https://github.com/j-piasecki) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2671](https://github.com/software-mansion/react-native-gesture-handler/pull/2671)

#### 🐛 Bug fixes

- Fix double `onPress` by [@&#8203;m-bert](https://github.com/m-bert)
in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2657](https://github.com/software-mansion/react-native-gesture-handler/pull/2657)
- Fix tvOS build by [@&#8203;m-bert](https://github.com/m-bert) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2654](https://github.com/software-mansion/react-native-gesture-handler/pull/2654)
- Add a non-null assertion for
`reactApplicationContext.javaScriptContextHolder` by
[@&#8203;UNIDY2002](https://github.com/UNIDY2002) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2662](https://github.com/software-mansion/react-native-gesture-handler/pull/2662)
- Move REACT_NATIVE_VERSION to native-only code by
[@&#8203;chriscoomber](https://github.com/chriscoomber) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2666](https://github.com/software-mansion/react-native-gesture-handler/pull/2666)
- Fix selection when TalkBack is enabled by
[@&#8203;m-bert](https://github.com/m-bert) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2669](https://github.com/software-mansion/react-native-gesture-handler/pull/2669)
- Fix inputs focus on safari by
[@&#8203;m-bert](https://github.com/m-bert) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2675](https://github.com/software-mansion/react-native-gesture-handler/pull/2675)

#### 🔢 Miscellaneous

- Bump browserify-sign from 4.2.1 to 4.2.2 in /example by
[@&#8203;dependabot](https://github.com/dependabot) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2661](https://github.com/software-mansion/react-native-gesture-handler/pull/2661)
- Change Alert to console.log in new docs by
[@&#8203;m-bert](https://github.com/m-bert) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2670](https://github.com/software-mansion/react-native-gesture-handler/pull/2670)
- Remove `handlersToCancel` on web. by
[@&#8203;m-bert](https://github.com/m-bert) in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2679](https://github.com/software-mansion/react-native-gesture-handler/pull/2679)

#### New Contributors

- [@&#8203;chriscoomber](https://github.com/chriscoomber) made their
first contribution in
[https://github.com/software-mansion/react-native-gesture-handler/pull/2666](https://github.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 <valorabot@valoraapp.com>
m-bert added a commit that referenced this pull request Feb 9, 2024
## 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>
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.

4 participants