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 got these results on my fone. One using flex:1 and the other using (as above) flex:0.92 (empirically found) !

fone with flex:1

fone with flex:0.92

note that if you edit the file (anything as a space in a comment) and save the title is ok!

flex:1 after minor change in the text and save ( NO REBUILD)

on my tablet the result for flex:0.92 is almost the same as with flex:1 on the fone. 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.

tablet with flex:0.88

I'll appreciate a lot your help.

===============================

Solution can be found with API react-native-safe-area-context Here the complete functional demo program:

// App.js

import React from 'react';
import { Text, View } from 'react-native';
import {SafeAreaProvider,useSafeAreaInsets,} from 'react-native-safe-area-context';

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { registerRootComponent } from 'expo';

const Stack = createNativeStackNavigator();

// --------------------
// ÉCRAN D’ENTRÉE
// --------------------

const EntreeScreen = ({ navigation, route }) => {
  const insets = useSafeAreaInsets();

  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'row',
        backgroundColor: 'blue',
        // Marges dynamiques selon les safe areas
        paddingBottom: insets.bottom,
        paddingLeft: insets.left,
        paddingRight: insets.right,
      }}
    >
      {/* Colonne de gauche */}
      <View style={{ flex: 1, backgroundColor: 'white' }} />

      {/* Colonne centrale */}
      <View
        style={{
          flex: 5,
          flexDirection: 'column',
          justifyContent: 'center',
          backgroundColor: 'lightblue',
          marginTop: 0,
          borderRadius: 32,
          borderWidth: 1,
          borderColor: 'red',
        }}
      >
        <Text
          style={{
            textAlign: 'center',
            fontSize: 32,
            fontWeight: 'bold',
          }}
        >
          CENTER TEXTE !
        </Text>
      </View>

      {/* Colonne de droite */}
      <View style={{ flex: 1, backgroundColor: 'white' }} />
    </View>
  );
};

// --------------------
// APP PRINCIPALE
// --------------------

const App = () => (
  <SafeAreaProvider>
    <NavigationContainer>
      <Stack.Navigator
        screenOptions={{
          title: '',
          headerStyle: {
            backgroundColor: 'lightgreen',
          },
          headerTintColor: 'red',
        }}
      >
        <Stack.Screen
          name="E"
          component={EntreeScreen}
          options={({ navigation, route }) => ({
            headerLeft: () => <Text style={{ marginLeft: 8 }}>LEFT</Text>,
            headerTitle: () => (
              <Text
                style={{
                  fontWeight: 'bold',
                  textAlign: 'center',
                  fontStyle: 'italic',
                }}
              >
                MON TITRE!
              </Text>
            ),
            headerRight: () => <Text style={{ marginRight: 8 }}>RIGHT</Text>,
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  </SafeAreaProvider>
);

registerRootComponent(App);


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