question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[3.7.0] Android change broke with appcompat >= 1.3.x and detachPreviousScreen

See original GitHub issue

Description

https://github.com/software-mansion/react-native-screens/pull/1066 introduced a bug that causes inactive screen to end up stuck when detachPreviousScreen is used alongside androidx.appcompat:appcompat 1.3.1 or above. The issue does not happen when using the outdated 1.1.0 version.

Screenshots

Screenshot_20210910-154421

Steps To Reproduce

Force a compat version greater than 1.1, e.g.,

implementation('androidx.appcompat:appcompat') {
        version {
            strictly '1.3.1'
        }
    }

Use stack navigator with detachPreviousScreen, e.g.,

/* eslint-disable react/display-name */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Text, View, StyleSheet,TouchableOpacity, StatusBar, Animated } from 'react-native';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';


const animationConfig = {
  animation: 'spring',
  config: {
    stiffness: 1100,
    damping: 100,
    mass: 3,
    overshootClamping: true,
    restDisplacementThreshold: 10,
    restSpeedThreshold: 10,
    useNativeDriver: true,
  },
};

//let animationInterp = CardStyleInterpolators.forHorizontalIOS; // use iOS slide as well on Android, looks cool
// https://reactnavigation.org/docs/stack-navigator/#animations
// https://github.com/react-navigation/react-navigation/blob/6cba517b74f5fd092db21d5574b558ef2d80897b/packages/stack/src/TransitionConfigs/CardStyleInterpolators.tsx#L14
const {multiply} = Animated;
const animationInterp = ({current, next, inverted, layouts: {screen}}) => {
  const translateFocused = multiply(
    current.progress.interpolate({
      inputRange: [0, 1],
      outputRange: [screen.width, 0],
      extrapolate: 'clamp',
    }),
    inverted,
  );

  const translateUnfocused = next
    ? multiply(
        next.progress.interpolate({
          inputRange: [0, 1],
          outputRange: [0, screen.width * -0.8],
          extrapolate: 'clamp',
        }),
        inverted,
      )
    : 0;

  const overlayOpacity = current.progress.interpolate({
    inputRange: [0, 1],
    outputRange: [0, 0.1],
    extrapolate: 'clamp',
  });

  const shadowOpacity = current.progress.interpolate({
    inputRange: [0, 1],
    outputRange: [0, 0.3],
    extrapolate: 'clamp',
  });

  return {
    cardStyle: {
      transform: [
        // Translation for the animation of the current card
        {translateX: translateFocused},
        // Translation for the animation of the card on top of this
        {translateX: translateUnfocused},
      ],
    },
    overlayStyle: {opacity: overlayOpacity},
    shadowStyle: {shadowOpacity},
  };
};

function ScreenX({navigation, route}) {
  return (
    <View style={styles.screenContainer}>
      <Text style={styles.paragraph}>
        This screen is used for testing bugs!
      </Text>
      <TouchableOpacity onPress={() => navigation.goBack()}><Text>Go Back</Text></TouchableOpacity>
      <TouchableOpacity onPress={() => navigation.navigate(route.params?.next)}><Text>Go Next</Text></TouchableOpacity>
    </View>
  );
}

function ScreenEnd({navigation}) {
  return (
    <View style={styles.screenContainer}>
      <Text style={styles.paragraph}>
        Final screen.
      </Text>
      <TouchableOpacity onPress={() => navigation.goBack()}><Text>Go back</Text></TouchableOpacity>
    </View>
  );
}



const Stack = createStackNavigator();

const StackNavigator = () => {

  return (
    <Stack.Navigator
      screenOptions={({navigation, route}) => {
        return {
          headerMode: 'screen',
          header: (props) => (
            <View style={styles.headerStyle}>
              <Text>{props.options.title}</Text>
            </View>
          ),
          gestureEnabled: false,
          cardOverlayEnabled: true,
          transitionSpec: {
            open: animationConfig,
            close: animationConfig,
          },
          cardStyleInterpolator: animationInterp,
        };
      }}>
      <Stack.Screen
        name="Screen1"
        component={ScreenX}
        options={({navigation, route}) => {
          return {
            title: 'Home',
            header: (props) => (
              <View style={styles.headerStyle}>
                <Text>Home Test</Text>
              </View>
            ),
          };
        }}
        initialParams={{ next: 'Screen2'}}
      />
      <Stack.Screen
        name="Screen2"
        component={ScreenX}
        options={{title: 'Screen2'}}
        initialParams={{ next: 'Screen3'}}
      />
      <Stack.Screen
        name="Screen3"
        component={ScreenX}
        options={{
          title: 'Screen3',
          headerShown: false,
          animationEnabled: true,
          detachPreviousScreen: false,
        }}
        initialParams={{ next: 'Screen4' }}
      />
      <Stack.Screen
        name="Screen4"
        component={ScreenEnd}
        options={{title: 'Screen4'}}
      />
    </Stack.Navigator>
  );
};


export default function BuggedStack() {
  return (
    <View style={styles.container}>
      <NavigationContainer>
        <StackNavigator />
      </NavigationContainer>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: StatusBar.currentHeight,
  },
  screenContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'black',
    padding: 24,
  },
  paragraph: {
    margin: 24,
    marginTop: 0,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  headerStyle: {
    height: 80,
    elevation: 3,
    top: 0,
    left: 0,
    right: 0,
    zIndex: 100,
    backgroundColor: 'black',
  }
});

Run the app and navigate next until the last screen.

Navigate back and observe

Expected behavior

Screen transitioning normally

Actual behavior

Screen stuck

Snack or minimal code example

https://github.com/cristianoccazinsp/react-native-screens/tree/bug-repro https://github.com/software-mansion/react-native-screens/compare/master...cristianoccazinsp:bug-repro?expand=1

Package versions

  • React: 17.0.2
  • React Native: 0.65.1
  • React Native Screens: 3.7.0

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:14 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
WoLewickicommented, Sep 15, 2021

Ok, let me know if you find out anything concerning the behavior in newer versions. I will also try and debug it a little in the free time, but I guess it won’t be easy to apply changes to the code such that it works correctly for all versions of appCompat.

1reaction
cristianoccazinspcommented, Sep 15, 2021

I’m using detachPreviousScreen for a totally non-related issue. Basically, I have one screen that renders stuff in a modal and exposes that feature through its context. However, that screen may sometimes take the user to a second screen that in turn may request the previous screen to show its modal (odd I know!).

So without detachPreviousScreen, the modal cannot be shown because the previous screen is taken out of the rendering tree. I ended up using detachPreviousScreen only on iOS, as on Android, it does show even if the previous screen is detached. This difference in behaviour is also a bit odd, but not a big deal.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Getting error while running `react-native run-android`
The fix is simply to specify android core version via androidXCore in build. ... to implementation 'androidx.appcompat:appcompat:1.3.0'.
Read more >
react-native-screens - bytemeta
SearchBar not appearing · Swipe to go back gesture on iOS does not work properly · [3.7.0] Android change broke with appcompat >=...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found