software-mansion/react-native-screens

Touch events do not work when multiple screens are active on Android

Closed this issue Β· 43 comments

I added a way to have transparent scenes in react-navigation a while back to support dialogs (https://github.com/react-navigation/react-navigation-stack/blob/master/src/views/StackView/StackViewCard.js#L35). It keeps all screens active in the stack but this seems to break touch events on Android because of the logic here: https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.java#L189

The react-navigation code is basically this:

const modalNavigatorConfig = {
  initialRouteName: 'app',
  mode: 'modal',
  headerMode: 'none',
  transparentCard: true, // <- The option for transparent card
  defaultNavigationOptions: {
    gesturesEnabled: false,
  },
  transitionConfig: () => ({
    transitionSpec: {
      duration: 300,
      easing: Easing.inOut(Easing.ease),
      timing: Animated.timing,
    },
    screenInterpolator: sceneProps => {
      const { position, scene } = sceneProps;
      const { index } = scene;

      const opacity = position.interpolate({
        inputRange: [index - 1, index],
        outputRange: [0, 1],
      });

      return { opacity };
    },
  }),
};

const ModalStack = createAppContainer(
  createStackNavigator(
    {
      app: {
        screen: RootStack,
      },
      { screen: SomeOtherRoute },
    },
    modalNavigatorConfig,
  ),
);

+1 We recently upgraded to React Navigation 3.X and it looks like all 3.X compatible versions of react-native-screens have this issue. We use transparentCard for our dialog system as well.

Think I'm seeing a similar issue on iOS: when navigating back to a previous screen in a stack that has transparentCard set, the screen doesn't respond to touch events. Had a poke around with the Element Inspector and it seems that the component tree is "StackViewLayout > ScreenContainer" after navigating back, compared to "StackViewLayout > SceneView > [ Screen ]" initially. Navigation gestures still work, screen focus events still trigger and navigating from the screen via code is fine. Will investigate further and try to get a snack up and running asap

@GfxKai I'm running into the same issue exactly, yet I haven't been able to replicate it in a snack. I can confirm that the component tree ends in ScreenContainer, and that toggling transparentCard to false on the StackNavigator fixes the component tree and resolves the unresponsiveness.

Yeah I couldn't reproduce in a snack either. Not sure why ☹️

I think the issue is that whenever a screen is added to a stack, the block here loops through all screens that are already mounted and active (usually only the screen immediately below the one being added to the top of the stack) and disables user interaction for the duration of the transition. This block checks for the end of the transition whenever a screen is added or removed (by waiting for there to only be 1 active screen) and then re-enables user interaction on the top-most screen.

Since (I think) the transparentCard prop keeps all screens in the stack active, once you add a third screen to a transparent stack, user interaction for the two screens underneath is disabled and never re-enabled again. Hence, when you pop that third screen, you are left with an unresponsive screen underneath.

Unfortunately I really don't know enough Obj-C to come up with a solution other than just allowing user interaction during transition 😬 I imagine we need some other way of detecting a finished transition other than checking the number of active screens. Perhaps it's possible to watch for a transition finish using onDidFocus on the js side then set a prop on the UIView over the bridge? πŸ€·β€β™‚οΈ

react-native-screens disable touch interaction for the duration of screen transition (so that you can't interact with buttons on the screen that is going away). The way we detect screen transition now is when there are two or more active screens. I believe that is the case when you have a transaprent dialog too. This would need to be changed in screen implementation (both on Android and iOS)

@kmagiera

This would need to be changed in screen implementation (both on Android and iOS)

Is this change possible in react-native-screens or it needs to be done in the respective OSes?

if somebody wants a quick repo to see this I added it here : #79

@ovy9086 - let's keep this all within this thread as it's the same underlying issue.

@ovy9086's other post below


When using react-native-screens with react-navigation 3 , enabling both useScreens() and transparentCard in the createStackNavigator options causes weird issues.

On iOS, if having more than 3 routes in the StackNavigator, when going back to the 2nd screen, you will not be able to interact the view anymore. Apparently something blocks it.
On Android, the 2nd screen is not clickable from the first time you navigate to it.

Here is a repo to reproduce this : https://github.com/ovy9086/react-nav-transparent-card-bug

@kmagiera What was the reason for moving touch event handling inside rn-screens vs using the pointerEvents from JS that react-navigation sets like it was before?

react-native-screens disable touch interaction for the duration of screen transition [...] The way we detect screen transition now is when there are two or more active screens

This seems contradictory to the documentation:

It it possible to have as many active children as you'd like but in order for the component to be the most effictent we should keep the number of active screens to the minimum.

I found this commit caused a similar problem in the FluidTransitions package. Since that change was introduced in alpha-18, I use alpha-17 to work around the problem.

@olegbl - indeed it does seem that the readme has gone out of sync with changes in the project, such is the life of an alpha version i suppose ;) @kmagiera has been quite busy recently, but perhaps in a couple weeks he can get back to it

still no updates on this ? :)

react-native-screens disable touch interaction for the duration of screen transition (so that you can't interact with buttons on the screen that is going away). The way we detect screen transition now is when there are two or more active screens. I believe that is the case when you have a transaprent dialog too. This would need to be changed in screen implementation (both on Android and iOS)

I'm using:
Expo SDK 32
React Native Screens 1.0.0-alpha.19
React Navigation 3.0.9
And the screens are all frozen when I have a TabNavigator inside of a TabNavigator (this happened after upgrading from expo SDK 31 to 32)

I have this same issue, remove of useScreens(); fixed this problem

I'm using the same workaround as @marcinj1, but it makes me sad, of course. πŸ˜”

That is not a work arround @marcinj1 and @brettdh that means that you don't make use of this package.
It's sad that it basically is broken because of this and I don't see why it is inclued in expo when it clearly is not stable

I'm also facing this issue.

Configuration

Expo SDK 32
React Native Screens imported from Expo
React Navigation 3.9.1
1 tab navigator with 4 tabs

To reproduce:

  1. Open app - tab 1 is open by default
  2. Navigate to tab 2 - everything on tab 2 is responsive as expected
  3. Navigate back to tab 1 - everything still responsive
  4. Navigate back to tab 2 - screen is completely unresponsive. SceneView + children have been replaced by ScreenContainer --> RNSScreenContainer

Resolution

Remove react-native-screens until the issue is fixed :(

Same issue.

Weird expo would include such an unstable library. I’d say this issue will only become more common with people trying to improve performance.

Removing react-native-screens until a fix is added. So sad though.

We have the same problem with useScreens() + transparentCard: true

I have the same problem, too

Same here ... :(

Facing the same problem here. Will this be fixed with SDK 33?

Same problem here, removing react-native-screens solved the problem.

After upgrading to Expo 33, my original issue no longer occurred on iOS. I did however have a problem with nested ScrollViews/FlatListsβ€”I could scroll them, but the list items were not tappable. I resolved that by adding keyboardShouldPersistTaps="always" to the nested ScrollViews/FlatLists.

I'm still facing issues on Android

TouchableOpacity components are not responding to touch in any nested screens on Android when using transparentCard : true within the createStackNavigator config.

I did a little expirement and changed this line from Screen.java :

@Override
public PointerEvents getPointerEvents() {
    return mTransitioning ? PointerEvents.NONE : PointerEvents.AUTO;
}

to :

@Override
public PointerEvents getPointerEvents() {
    return PointerEvents.AUTO;
}

This seems to resolve the issue as all the views will be able to receive touch events even when "transitioning" and I haven't seen any side effects when it comes to performance. I might make a PR with a config to override the default behaviour of this method...

sijad commented

any news on this one?

shouldn't this kind of stuff handled in navigation itself? using pointerEvents prob for example.

sijad commented

I just wanted to mention this one more time, I think this kind of stuff should be implemented in navigation level (probably in js), it doesn't feel any good to have debouncing only with useScreens();.

Same issue here useScreens() + transparentCard: true.
And if we set transparentCard as false, a white screen is rendered for a small moment before the modal its rendered

Anyone had the chance to look into if there is a similar solution in iOS for @emeraldsanto's changes?

I change the import of the StackNavigator from import { createStackNavigator } from 'react-navigation-stack'; to the one that say this library
import createNativeStackNavigator from 'react-native-screens/createNativeStackNavigator';
and it's working for me

@alexpareto I can look into it but according to my testing the events are still triggered on iOS without changing anything. I found the piece of code managing the touch events in RNSScreenContainer.m:

    // if we are down to one active screen it means the transitioning is over and we want to notify
    // the transition has finished
    if ((activeScreenRemoved || activeScreenAdded) && _activeScreens.count == 1) {
        RNSScreenView *singleActiveScreen = [_activeScreens anyObject];
        // restore interactions
        singleActiveScreen.userInteractionEnabled = YES;
        [singleActiveScreen notifyFinishTransitioning];
    }

It seems to be pretty much the same code as Android, the difference is most likel in the calculation of active screens. I can check it out and report back here if I find anything.

@15matias15's solution (to use the in-package createNativeStackNavigator) is the right solution for now, but it breaks when using react-navigation-stack 2.0 (currently in alpha). I opened #173 to track

@emeraldsanto's solution worked on Android

For iOS, I fixed it by disabling the logic which disables interactions during a transition. I did this by going to RNSScreenContainer.m and changed

screen.userInteractionEnabled = NO;

to

screen.userInteractionEnabled = YES;

Still looking for a solution that works on Android...

Any update on this? This is happening to me as well on iOS while using enableScreens(); and transparentCard: true together.

This doesn't seem to be an issue for me anymore after upgrading to the following versions:

"react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz", // or 0.62.2
"react-native-screens": "~2.9.0",
"react-navigation": "^4.0.10",

Hope that can help

I had same issue, reinstalling the react-native-screens resolved the issue.

I'm running into this issue with RN Screens v3. I'm building a pager with RN Screens but once I want to have more than one page active at the same time, touches are not available anymore.

Only one Screen component of ScreenContainer can have activityState with value 2 at the same time and that Screen is responsive.

Thanks @WoLewicki, my use case is a large list where I would like to offload the parts of the list that are not visible.
The prototype has wraps a Screen component per Item but that means that only one Item is touchable. am I approaching this correctly? Or this is simply not a use case for react native screens?

I am afraid it is not the use case, at least not for now. react-native-screens is meant to expose one Screen component for all of the content you are interacting with at the moment, but you can play with performUpdate method of ScreenContainer.java and updateContainer of ScreenContainer.m to try and change the attaching/detaching logic according to your use case.