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

Some touchable components are not working (Native Android devices only) #44643

Closed
DrZoidberg09 opened this issue May 21, 2024 · 32 comments
Closed
Labels
Platform: Android Android applications. Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules)

Comments

@DrZoidberg09
Copy link

DrZoidberg09 commented May 21, 2024

Description

For me this issue still the issue #36710 persists on RN 0.74.1, Expo 51.0.8 and react-native-screens 3.31.1 running with fabric.

Same problem: Some components, e.g. custom buttons in the header bar or some modals (not all). This happens only on native Android devices. iOS or Android emulator are fine.

Additionally, native buttons work. Only "normal" RN components don't.

onPressIn works fine, normal onPress does not.

Steps to reproduce

Have the mentioned setup. Use Expo Router with custom buttons or wix rnui ActionSheet (therefore probably not related to third party libs)

React Native Version

0.74.2

Affected Platforms

Runtime - Android

Areas

Fabric - The New Renderer

Output of npx react-native info

System:
  OS: macOS 14.4.1
  CPU: (8) arm64 Apple M1
  Memory: 89.05 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 21.7.3
    path: /opt/homebrew/bin/node
  Yarn:
    version: 1.22.19
    path: ~/.yarn/bin/yarn
  npm:
    version: 10.7.0
    path: /opt/homebrew/bin/npm
  Watchman:
    version: 2024.05.06.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /usr/local/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.2
      - iOS 17.2
      - macOS 14.2
      - tvOS 17.2
      - visionOS 1.0
      - watchOS 10.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2022.3 AI-223.8836.35.2231.10811636
  Xcode:
    version: 15.2/15C500b
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.8
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.74.1
    wanted: 0.74.1
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

No relevant log?

Reproducer

https://github.com/DrZoidberg09/RN-Android-Touch-Issue/

Screenshots and Videos

No response

@DrZoidberg09 DrZoidberg09 added Needs: Triage 🔍 Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules) labels May 21, 2024
@github-actions github-actions bot added Needs: Author Feedback Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. Platform: Android Android applications. labels May 21, 2024
@cortinico
Copy link
Contributor

Will add, if needed

Can we get a reproducer? As otherwise it's extremely hard to debug this issue.

@DrZoidberg09
Copy link
Author

Yes, will follow later today.

@github-actions github-actions bot added Needs: Attention Issues where the author has responded to feedback. and removed Needs: Author Feedback Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. labels May 22, 2024
@cortinico cortinico removed the Needs: Attention Issues where the author has responded to feedback. label May 28, 2024
@cortinico
Copy link
Contributor

Yes, will follow later today.

Any news on this?

@cortinico cortinico added the Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. label May 28, 2024
Copy link

⚠️ Missing Reproducible Example
ℹ️ We could not detect a reproducible example in your issue report. Please provide either:

@DrZoidberg09
Copy link
Author

I edited the original post. This is the repro: https://github.com/DrZoidberg09/RN-Android-Touch-Issue/

@github-actions github-actions bot added Needs: Attention Issues where the author has responded to feedback. and removed Needs: Author Feedback labels May 28, 2024
@tcloudAce
Copy link

tcloudAce commented May 30, 2024

same issue !! onPressIn and onPressOut is work, but onPress not work only!

@DrZoidberg09
Copy link
Author

@cortinico Anything else you need from me to look into this?

@imransilvake
Copy link

imransilvake commented Jun 1, 2024

on Samsung devices onPress doesn't work. All customers with Samsung devices (S23, S24) are reporting it. any solution to make it work?

@copiri-six
Copy link

Just chiming in to say that we have been struggling with this as well. Both this issue and #36710 describe our problem quite well, although #36710 got hijacked and closed based on an unrelated bug being addressed (the solved bug had to do with the new architecture, while the OP stated his problem existed before the new architecture was even created).

Looking forward to any movement on this issue, because its random nature makes it effectively impossible to reliably reproduce.

@tcloudAce
Copy link

In my scenario, I can reproduce this error 100% of the time (onPress does not respond, but interestingly, the animation effect of TouchableOpacity still shows). I have tested it with Expo 51, Expo 50, React Native 0.74.1, and React Native 0.73.6. The test environment includes Samsung S23 and S24. In a real device environment, even onPressIn and onPressOut do not work, whereas on the emulator, onPressIn and onPressOut respond normally, but onPress does not respond.

My scenario is similar to a step-by-step installation guide Page, which is built with a ScrollView in RN. There are about 7 pages within the ScrollView. If a Modal is opened within these pages, the onPress of buttons inside the Modal does not respond. Currently, I have disabled react-native-screens and used onPressOut instead of onPress. Although it works, it causes accidental clicks when scrolling (due to onPressOut).

Additionally, to add more context, my ScrollView contains nested ScrollViews. In this environment, onPress 100% fails to respond. The issue does not occur with non-nested ScrollViews.

@tcloudAce
Copy link

tcloudAce commented Jun 3, 2024

@imransilvake @DrZoidberg09 Using react-native-handler-gesture to build a button can temporarily solve this problem. Here is the sample code:
import React from "react";
import { TapGestureHandler, State } from "react-native-gesture-handler";
import { View } from "react-native";

export default function Pressable({ onPress, children, style, disabled }) {
const onHandlerStateChange = (event) => {
if (event.nativeEvent.state === State.END) {
onPress();
}
};

return (

{children}

);
}

@tcloudAce
Copy link

@cortinico In my scenario, the TextInput also cannot be focused. This is indeed a peculiar issue.

@gtwilliams03
Copy link

I have the same issue with our app. iOS and Android emulator work fine. iOS production app works fine. Recently though, the Android production app has become relatively unusable. You can log in and within a few seconds, pretty much every pressable item becomes unresponsive.

@tcloudAce
Copy link

@gtwilliams03 If the buttons are not working, you can try the following code:
`import React from "react";
import { TapGestureHandler, State } from "react-native-gesture-handler";
import { View } from "react-native";

export default function Pressable({ onPress, children, style, disabled }) {
const onHandlerStateChange = (event) => {
if (event.nativeEvent.state === State.END) {
onPress();
}
};

return (

{children}

);
}`

@gtwilliams03
Copy link

gtwilliams03 commented Jun 4, 2024

@tcloudAce Thank you - however this issue is pretty much with anything pressable (not just a button). Buttons, navigation tabs, etc. I should also add that the app responds to presses for a very short period of time (<3 seconds) and then ceases responding to any taps anywhere on the Android device screen. The emulators work fine so this is very difficult to reproduce/test.

@tcloudAce
Copy link

In my case, changing the component layout and applying the above code temporarily solved my problem. The only thing that is certain is that iOS and Android often cannot use the same layout. Code that runs successfully and without issues on Android usually works on iOS, but not the other way around. Moreover, this issue is not only present in the latest versions of React Native but has existed in many past versions as well. I hope this helps everyone.

@cortinico
Copy link
Contributor

DrZoidberg09/RN-Android-Touch-Issue

Can we get a non-expo reproducer using this template instead:
https://github.com/react-native-community/reproducer-react-native

The provided repro is quite involved (uses expo router) and I'd like to isolate this issue as much as possible.

@DrZoidberg09
Copy link
Author

Tried to reproduce it with pure RN and the repo you shared. So far, I could not reproduce it. Even with the RN-Navigation header issue shown in the other Expo based repro.

So far, it seems it's neither react-native nor react-navigation, but Expo(-router).

But I will keep trying to reproduce it in the next days.

@cortinico
Copy link
Contributor

So far, it seems it's neither react-native nor react-navigation, but Expo(-router).

That's sometimes related to react-native-screens (at least it was in the past). Worth a try with -screens to see if it reproduces there

@DrZoidberg09
Copy link
Author

I have react-native-screens and react-navigation stack also running in the repro. Here it works fine. In the main project with Expo it is not.

Another weird behavior:

If I run Expo development build on the native device most is working pretty ok besides onPress in the header bar. (onPressOut and In is working nicely though). However, if I build a proper apk and run it on the native (Samsung) device. Everything pressable in a stack is not working, incl. every list, button, etc. (Also the positioning of position absolute items is different if I use development build or an apk on the same device...)

@DrZoidberg09
Copy link
Author

This seems to be very close to the issue as well: software-mansion/react-native-screens#2150

@DrZoidberg09
Copy link
Author

@cortinico I could track it down to expo-router. Please look at the github repo. I added a reproducer for each: pure RN, expo with router and expo without router.

The two repos without router work. With router it does not.

@cortinico
Copy link
Contributor

The two repos without router work. With router it does not.

Great so let's close it for now and move the conversation over to: expo/expo#30032

@cortinico cortinico removed Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. Needs: Attention Issues where the author has responded to feedback. labels Jun 27, 2024
@tremblerz
Copy link

I was encountering a similar issue -- TouchableOpacity (and other wrappers of Pressable) not working on Android but working fine in iOS. However, I was able to fix the issue by adding a zIndex: 10 in the style property. 10 is just a number here, basically it had to be on the top.

@littleski
Copy link

littleski commented Aug 19, 2024

I have done a little digging into Pressability.js
It seems that the condition to fire onPress is: isPressInSignal(prevState) && signal === 'RESPONDER_RELEASE'
and then that !isPressCanceledByLongPress

In my testings of this wonky behaviour.

  • Sometimes, the events are in the right order (it's rare, but it is happening). (i get RESPONDER_ACTIVE_PRESS_IN as prevState)
  • Sometime not at all, and when i get the RESPONDER_RELEASE signal, the prevState is RESPONDER_ACTIVE_PRESS_OUT and so onPress do not fire.
const isPressInSignal = (signal: TouchState) =>
  signal === 'RESPONDER_INACTIVE_PRESS_IN' ||
  signal === 'RESPONDER_ACTIVE_PRESS_IN' ||
  signal === 'RESPONDER_ACTIVE_LONG_PRESS_IN';

onPressIn and onPressOut are not using the same State machine conditions, and thus, are not impacted by the issue.

Haven't fixed (as adding the prev event to the function is surely not what is supposed to happen), and i am trying to understand a bit more to not do shit, but maybe it can help someone understand more the issue.

@DominikHinc
Copy link

DominikHinc commented Aug 21, 2024

I investigated it a little more and from what I see this is a case of measure not synchronizing correctly with the coordinates of touches.

On Android with new architecture the touches will be returned as coordinates relative to the root view. So if the screen you are using is 300 x 700 pixels, it will fall somewhere between that.

The Pressablity.js file, mentioned by @littleski then takes those touch coordinates and compares them to the coordinates of the responder (returned by the measure function),

 _measureResponderRegion(): void {
    if (this._responderID == null) {
      return;
    }

    if (typeof this._responderID === 'number') {
      UIManager.measure(this._responderID, this._measureCallback);
    } else {
      this._responderID.measure(this._measureCallback);
    }
  }
  _measureCallback = (
    left: number,
    top: number,
    width: number,
    height: number,
    pageX: number,
    pageY: number,
  ) => {
    if (!left && !top && !width && !height && !pageX && !pageY) {
      return;
    }
    this._responderRegion = { // <------ position of the responder relative to the root view (this is the problematic place, which is explained below
      bottom: pageY + height,
      left: pageX,
      right: pageX + width,
      top: pageY,
    };
  };

BUT ONLY on the responder move:

 onResponderMove: (event: PressEvent): void => {
        /* ... */
        const touch = getTouchFromPressEvent(event); // <--- coordinates of users touch
        /* ... */
        if (this._isTouchWithinResponderRegion(touch, responderRegion)) {
          this._receiveSignal('ENTER_PRESS_RECT', event);
        } else {
          this._cancelLongPressDelayTimeout();
          this._receiveSignal('LEAVE_PRESS_RECT', event);
        }
      },

  _isTouchWithinResponderRegion(
    touch: $PropertyType<PressEvent, 'nativeEvent'>,
    responderRegion: $ReadOnly<{|
      bottom: number,
      left: number,
      right: number,
      top: number,
    |}>,
  ): boolean {
   /* ... */
    return (
      touch.pageX > regionLeft &&
      touch.pageX < regionRight &&
      touch.pageY > regionTop &&
      touch.pageY < regionBottom
    );
  }

And that is why this issue only appears on some android devices which are more sensitive. Emulators and less sensitive devices will never trigger the onResponderMove callback. I found out that this issue can be reproduced on emulators just by moving the mouse a little bit during a touch.

As for the reason why this bug even occurs at all, it comes down to the data returned from the measure. The pageX and pageY in some special circumstances will return coordinates far exceeding the boundaries of the screen. From what I tested for now, this is a case for both react-native-tab-view and react-native-pager-view. I haven't found a time to look into them, what exact construct is causing that, but I know for sure that the responder pageX and pageY is being offset there by the amount of the travel distance it had to do before appearing on the screen.
Let's take tab-view for example:
If we have 3 tabs in tab-view , each screen-wide with it's own touchable button (responder), the responder coordinates for the first tab button will be normal - it will respond to touches correctly, but for the second and third tab the coordinates will be offset (pageX) by respectively (in case of 300 pixel wide screen) - 300 pixels and 600 pixels, meaning that as soon as the responder calls onResponderMove the touch will be broken (put into LEAVE_PRESS_RECT state) which it then has no way of fixing, since the X position of the touch will be like 112 pixels and the responder X will be 412 pixels (out of screen).

For now I don't know how to fix it.

@DominikHinc
Copy link

It's definitely a react-native-pager-view issue, since tab-view also relies on that, for now I simply swapped all the buttons in the app with this component

const ManuallyHandledButton = ({ onPress, children, ...props }) => {
  const _touchActivatePositionRef = useRef(null)

  const _onPressIn = useCallback((e) => {
    const { pageX, pageY } = e.nativeEvent

    _touchActivatePositionRef.current = {
      pageX,
      pageY
    }
  }, [])

  const _onPress = useCallback(
    (e) => {
      const { pageX, pageY } = e.nativeEvent

      const absX = Math.abs(_touchActivatePositionRef.current.pageX - pageX)
      const absY = Math.abs(_touchActivatePositionRef.current.pageY - pageY)

      const dragged = absX > PRECISION || absY > PRECISION

      if (!dragged) {
        onPress?.(e)
      }
    },
    [onPress]
  )

  return (
    <Pressable
      onResponderStart={_onPressIn}
      onResponderEnd={_onPress}
      {...props}
    >
      {children}
    </Pressable>
  )
}


const PressableForwarder =
  global?.nativeFabricUIManager && IS_ANDROID
    ? ManuallyHandledButton
    : TouchableOpacity

export default PressableForwarder

And it seems to work good enough

@littleski
Copy link

Pigging backing on @DominikHinc

UiManager

const UIManager: UIManagerJSInterface = {
  ...UIManagerImpl,
  measure(
    reactTag: number,
    callback: (
      left: number,
      top: number,
      width: number,
      height: number,
      pageX: number,
      pageY: number,
    ) => void,
  ): void {
    if (isFabricReactTag(reactTag)) {
      const FabricUIManager = nullthrows(getFabricUIManager());
      const shadowNode =
        FabricUIManager.findShadowNodeByTag_DEPRECATED(reactTag);
      if (shadowNode) {
        FabricUIManager.measure(shadowNode, callback);
      } else {
        console.warn(`measure cannot find view with tag #${reactTag}`);
        // $FlowFixMe[incompatible-call]
        callback();
      }
    } else {
      // Paper
      UIManagerImpl.measure(reactTag, callback);
    }
  },

So here is the difference between new arch and old which seems logic. need to dig a bit in the Java to understand the difference and what may have been broken in the new arch java binding.

@DominikHinc
Copy link

@littleski turns out all I had to do was to implement this callstack/react-native-pager-view#836

@littleski
Copy link

The repro doesn't use this. It's more than likely that this is just a workaround for one lib. I don't use this package either. Might help understand the issue tho.

@ap9101
Copy link

ap9101 commented Sep 13, 2024

i'm also faced same issue after enabling new architecture.
For those who are facing same issue in android and specifically in react native tab view then use this solution. upgrade react-native-pager-view to this version "^6.4.1" . Hope this will helps someone

@gtwilliams03
Copy link

gtwilliams03 commented Sep 23, 2024

@ap9101 Thank you for your comment, however, when I upgraded our project to react-native-pager-view to version ^6.4.1 the screen with the ViewPager errors out with requireNativeComponent: "LEGACY_RNCViewPager" was not found in the UIManager. I tried versions 6.3.1 and 6.4.1 - seems like anything higher than 6.3.0 throws this error for me. Installing via npx expo install react-native-pager-view forces you back to 6.3.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: Android Android applications. Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules)
Projects
None yet
Development

No branches or pull requests

10 participants