androidreact-nativeexporeact-navigationstatusbar

How to configure react-navigation to work with Android edge-to-edge after Expo SDK 53?


Android & Expo Go was working fine for me until Expo SDK 53 using @react-navigation/native.

As of SDK 53, Navigation is now working edge-to-edge, and that was not the way before, and I need to re-configure for this mode.

I checked forums and saw problems with SDK 53, but not mine. I also tried SafeViewProvider without success.

I hacked some code (thanks to ... forgot your name!) to show the result below. The problem is that my views now appear underneath the status bars due to the new edge-to-edge setting (they were fine in earlier versions). Edge-to-edge can no longer be turned off in Expo Go or newer versions of Android.

I started a new project with latest versions and everything else works fine.

import { Text, View, SafeAreaView } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { registerRootComponent } from "expo";

// Ajoute cette ligne pour forcer React Navigation à utiliser un header en pur JS
const Stack = createNativeStackNavigator({});

const EntreeScreen = ({ navigation, route }) => {
  return (
    <SafeAreaView style={{ flex: 0.92, flexDirection: "row", backgroundColor: "blue" }}>
      <View style={{ flex: 1, backgroundColor: "white" }} />
      <View
        style={{
          flex: 5,
          flexDirection: "column",
          justifyContent: "center",
          alignItems: "center",
          backgroundColor: "lightblue",
          marginTop: 0,
          justifyContent: "center",
          borderRadius: 32,
          borderWidth: 1,
          borderColor: "red",
        }}
      >
        <Text style={{ fontWeight: "bold" }}>CENTER TEXTE !</Text>
      </View>
      <View style={{ flex: 1, backgroundColor: "white" }} />
    </SafeAreaView>
  );
}

const App = () => (
  <NavigationContainer>
    <Stack.Navigator
      screenOptions={{
        title:"",
        headerStyle: {
          backgroundColor: "lightgreen",
        },
        headerTintColor: "#888",
        headerTitleStyle: {
          fontWeight: "normal",
          textAlign: "center",
        },
      }}
    >
      <Stack.Screen                     // 01 entrée
        name={"E"}
        component={EntreeScreen}
        options={({ navigation, route }) => ({
          headerLeft: () => (       // bouton aide
            <View style={{ marginTop: 24, alignItems: "center" }}>
              <Text>LEFT</Text>
            </View>
          ),
          headerTitle: () => (
            <View style={{ marginTop: 24 }}>
              <Text style={{ textAlign: "center" }}>MON TITRE!</Text>
            </View>
          ),
          headerRight: () => (
            <View style={{ marginTop: 24 }}>
              <Text>RIGHT</Text>
            </View>
          ),
        })}
      />
    </Stack.Navigator>
  </NavigationContainer>
);

registerRootComponent(App);

I tried using flex: 1 and flex: 0.92 (as above, empirically found) to fit edge-to-edge and got these results on my phone:

On my tablet, the result for flex: 0.92 is almost the same as with flex: 1 on the pone. With 0.88, it's quite good. Just the status bar is green and the navigation lower bar does not show the 3 buttons, just a grey banner.


Solution

  • According to the react-navigation docs on safe areas, you should use the react-native-safe-area-context library to support Android instead of React Native's default SafeAreaView:

    While React Native exports a SafeAreaView component, this component only supports iOS 10+ with no support for older iOS versions or Android. In addition, it also has some issues, i.e. if a screen containing safe area is animating, it causes jumpy behavior. So we recommend to use the useSafeAreaInsets hook from the react-native-safe-area-context library to handle safe areas in a more reliable way.

    We can adapt your code to use the SafeAreaProvider in the App component and then the useSafeAreaInsets hook in the EntreeScreen component as well as any additional components, such as a Header component with custom styles:

    import { registerRootComponent } from 'expo';
    import { View, Text } from 'react-native';
    import { NavigationContainer } from '@react-navigation/native';
    import { createNativeStackNavigator } from '@react-navigation/native-stack';
    import {
      SafeAreaProvider,
      useSafeAreaInsets,
    } from 'react-native-safe-area-context'; // <-- add these imports
    
    // Ajoute cette ligne pour forcer React Navigation à utiliser un header en pur JS
    const Stack = createNativeStackNavigator();
    
    const EntreeScreen = () => {
      const insets = useSafeAreaInsets(); // <-- add this hook
    
      return (
        <View
          style={{
            flex: 1,
            paddingBottom: insets.bottom, // -- add padding to all but the top, which the header already has
            paddingLeft: insets.left,
            paddingRight: insets.right,
          }}
        >
          <View
            style={{
              flex: 5,
              flexDirection: "column",
              justifyContent: "center",
              alignItems: "center",
              backgroundColor: "lightblue",
              marginTop: 0,
              borderRadius: 32,
              borderWidth: 1,
              borderColor:"red",
            }}
          >
            <Text style={{ fontWeight: "bold" }}>CENTER TEXTE !</Text>
          </View>            
        </View>
      );
    }
    
    const Header = ({text, style}) => {
      const insets = useSafeAreaInsets(); // <-- add this hook
    
      return <View
        style={{
          // this seems to only be needed on some devices before rotation? probably a bug
          // uncomment the line below if needed on your device
          // paddingTop: insets.top // <-- add padding to the top
        }}
      >
        <Text style={style}>{text}</Text>
      </View>
    }
    
    const App = () => (
      <SafeAreaProvider>
        <NavigationContainer >
          <Stack.Navigator
            screenOptions={{
              headerStyle: {
                backgroundColor: 'lightgreen',
              },
              headerTintColor: '#888',
              headerTitleStyle: {
                fontWeight: 'normal',
                textAlign:'center',
              },
            }}
          >
            <Stack.Screen                       // 01 entrée
              name={"E"}
              component={EntreeScreen}
              options={() => ({
                headerLeft: () => (     // bouton aide
                  <Header text="LEFT" />
                ),
                headerTitle: () => (
                  <Header style={{ textAlign: "center" }} text="MON TITRE!" />
                ),
                headerRight: () => (
                  <Header text="RIGHT" />
                ),
              })}
            />
          </Stack.Navigator>
        </NavigationContainer>
      </SafeAreaProvider>
    );
    
    registerRootComponent(App);
    

    I also wrote up an Expo Snack as a live demo: https://snack.expo.dev/@agilgur5/safe-area-edge-to-edge---so-question-79607558?platform=android. Here's a screenshot of the phone preview with the components fitting as desired with the top status bar, notch, and bottom navigation buttons:
    expo snack phone preview