Expo React Native StatusBar hidden but still showing whitespace

296 views Asked by At

I'm pretty sure this is my first question I'm posting on here, so I hope I do this properly and provide all of the relevant information.

Context:

This is a twofold personal project. I am building this app strictly for my partner and I, so no major constraints and I'm open to all suggestions/recommendations. Her and I both travel often and speak several foreign languages, but we are both still learning more everyday and wish for a personalized app we can both use and share together, so I began to create this app for our communication and language development skills.

This project also happens to be a great opportunity for me to learn and grow my skills with React Native & native Kotlin/Swift development (I tried JavaFx for a while, but I'm a full-time front end web developer so React Native seems like a much more natural approach for UI development and Kotlin/Swift for native/back end).

It's essentially just a study app, combining features I like from Quizlet, Anki, Pimsleur, etc.., and combining some chat functionality amongst other things, but that's out of scope for this issue. Please see my problem statement for the main issue.

Problem statement:

I am having an issue with the StatusBar at the top of the screen still leaving a blank white space after setting it to be hidden. This issue is persisting despite everything I've tried and researching already.

Goal:

I wish to have this display either the background color I desire when the StatusBar is hidden, or the ImageBackground depending on which activity/screen it's on. i.e. No white space or notch at the top of the screen.

What I've already tried:

  • I've already tried backgroundColor as transparent and set the translucent property to true.
  • In app.json I've tried configuring it to use dark for the userInterfaceStyle property.
  • I've tried using react-native-safe-area-context package with their SafeAreaProvider around the entire app and used SafeAreaView around the component itself (I'm aware that the regular SafeAreaView is for iOS only and I need this for Android specifically, I just thought maybe this context package was worth a shot though).
  • Set backgroundColor to black
  • Set style to dark and barStyle to dark-content
  • I've tried using expo-status-bar and react-native StatusBar (oddly enough, even though I'm using expo, I had even more styling issues with the expo-status-bar)

I'm running out of ideas to try.. None of the above did anything when implementing it. As soon as I hide the StatusBar, I always have that white space at the top and it's quite annoying.

Code:

app.json

{
  "expo": {
    "name": "...",
    "slug": "...",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "dark",
    "splash": {
      "image": "./assets/....gif",
      "resizeMode": "contain",
      "backgroundColor": "#000000"
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com...."
    },
    "android": {
      "package": "com....",
      "adaptiveIcon": {
        "foregroundImage": "./assets/....gif",
        "backgroundColor": "#000000"
      }
    },
    "androidStatusBar": {
      "barStyle": "dark-content",
      "backgroundColor": "#000000",
      "hidden": true,
      "translucent": true
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

Flashcards.js

import { useState, useEffect } from 'react';
import { View, Text, Pressable, StyleSheet, FlatList, Dimensions, ImageBackground, StatusBar } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from '../assets/....gif';

const DEVICE_WIDTH = Dimensions.get('window').width;

const SAMPLE = [
  {
    id: 0,
    name: "Русские карточки",
    description: "These are sample Russian flashcards.",
  },
  {
    id: 1,
    name: "Flashcards tiếng Việt",
    description: "These are sample Vietnamese flashcards.",
  },
  {
    id: 2,
    name: '中文抽認卡',
    description: 'These are sample Chinese flashcards.',
  },
  {
    id: 3,
    name: 'כרטיסי פלאש בעברית',
    description: 'These are sample Hebrew flashcards.',
  },
  {
    id: 4,
    name: 'Data structures',
    description: 'These are sample data structure flashcards.',
  },
  {
    id: 5,
    name: 'Countries',
    description: 'These are sample culture flashcards from countries around the world.',
  },
  {
    id: 6,
    name: 'Cultures',
    description: 'These are sample cultural tradition flashcards.',
  },
  {
    id: 7,
    name: 'Exercises',
    description: 'These are sample flashcards about healthy exercises.',
  },
  {
    id: 8,
    name: 'Final',
    description: 'This is the final collection just being used to see if it will make it scrollable.',
  }
];

export default function Flashcards({ navigation }) {
  const [collections, setCollections] = useState([]);
  const [selectedCollection, setSelectedCollection] = useState(null);

  useEffect(() => {
    setCollections(SAMPLE)
  }, [])

  const Item = ({ name, onPress, description }) => (
    <Pressable onPress={onPress} style={styles.item}>
      <Text style={styles.itemTitle}>{name}</Text>
      <Text style={styles.itemDescription}>{description}</Text>
    </Pressable>
  );

  const handleClick = (item) => {
    setSelectedCollection(item.id);
    navigation.navigate('StudyMode', {
      name: item.name,
    });
  };

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar
        hidden
        backgroundColor="black"
        // translucent
        style="dark"
        barStyle="dark-content"
      />
      <ImageBackground
        source={Logo}
        style={styles.image}
        resizeMode="cover"
      >
        <View style={styles.titleContainer}>
          <Text style={styles.title}>Flashcards</Text>
        </View>
        <View style={styles.collectionsContainer}>
          <FlatList
            data={SAMPLE}
            style={styles.flatlist}
            renderItem={({ item }) => {
              return <Item onPress={() => handleClick(item)} name={item.name} description={item.description} />
            }}
            keyExtractor={item => item.id}
          />
        </View>
      </ImageBackground>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    marginTop: StatusBar.currentHeight + 10 || 10,
    backgroundColor: '#000000',
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 60,
  },
  image: {
    flex: 1,
    justifyContent: 'center',
    contentFit: 'cover',
  },
  titleContainer: {
    borderBottomWidth: 6,
    borderColor: 'red',
    borderRadius: 12,
    backgroundColor: 'black',
    justifyContent: 'center',
    alignItems: 'center',
    marginLeft: 12,
    marginRight: 12,
    marginTop: 14,
    marginBottom: 10,
  },
  title: {
    color: 'white',
    fontFamily: 'Agright',
    fontSize: 32,
  },
  collectionsContainer: {
    borderWidth: 6,
    borderColor: 'red',
    borderRadius: 12,
    backgroundColor: 'black',
    width: DEVICE_WIDTH - 10,
  },
  flatlist: {
    flexGrow: 0,
    height: '100%',
  },
  item: {
    borderRadius: 12,
    borderWidth: 2,
    borderColor: 'red',
    width: '99%',
    alignItems: 'center',
    marginLeft: 2,
    marginRight: 2,
    marginTop: 6,
    marginBottom: 6,
    paddingVertical: 6,
    backgroundColor: '#000000',
  },
  itemTitle: {
    fontFamily: 'Agright',
    fontSize: 26,
    color: 'white',
  },
  itemDescription: {
    fontFamily: 'Agright',
    fontSize: 13,
    color: 'white',
  }
});

App.js

import { useState, useEffect, useContext } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
import { Platform, NativeModules } from 'react-native';
import { IntlProvider } from 'react-intl';
import en from './locales/en.json';
import he from './locales/he.json';
import ru from './locales/ru.json';
import uk from './locales/uk.json';
import vi from './locales/vi.json';
import zh from './locales/zh.json';

/**
 * Components
 */
import Login from './screens/Login';
import Home from './screens/Home';
import Flashcards from './screens/Flashcards';
import StudyMode from './screens/StudyMode';
import Texts from './screens/Texts';
import Translator from './screens/Translator';
import Dictionary from './screens/Dictionary';
import Chat from './screens/Chat';

/**
 * Hooks
 */
import useFonts from './hooks/useFonts';

/**
 * Context
 */
import { LocaleContextProvider, LocaleContext } from './context/LocaleContext';
import { DbContextProvider } from './database/DbContext';

/**
 * Messages (Localization)
 */
const messages = {
  'en': en,
  'en_US': en,
  'he': he,
  'ru': ru,
  'uk': uk,
  'vi': vi,
  'zh': zh,
};
const devicePlatform = Platform.OS;
const _deviceLocale = devicePlatform === 'android' ?
  NativeModules.I18nManager.localeIdentifier :
  NativeModules.SettingsManager.settings.AppleLocale ||
  NativeModules.SettingsManager.settings.AppleLanguages[0];
const deviceLocale = _deviceLocale.split(/[-_]/)[0];

SplashScreen.preventAutoHideAsync();

const Stack = createNativeStackNavigator();
const screenOptions = {
  headerStyle: {
    backgroundColor: '#000000',
  },
  headerTintColor: '#ffffff',
  headerTitleStyle: {
    fontFamily: 'Agright',
    textAlign: 'center'
  },
  headerTitleAlign: 'center',
};

export default function App() {
  const [appIsReady, setAppIsReady] = useState(false);
  const [isAuthNd, setIsAuthNd] = useState(false);
  const [isVisible, setIsVisible] = useState(false);
  const [fontsLoaded] = Font.useFonts({
    Agright: require('./assets/fonts/AgrightRegular-qZ5dr.otf'),
  })

  const { changeLanguage, currentLang } = useContext(LocaleContext);

  const loadFonts = async () => {
    try {
      await Font.loadAsync({
        Agright: require('./assets/fonts/AgrightRegular-qZ5dr.otf'),
        Fridays: require('./assets/fonts/Fridays-AWjM.ttf'),
        MyChemicalRomance: require('./assets/fonts/MyChemicalRomance-1X5Z.ttf'),
        Toxia: require('./assets/fonts/Toxia-OwOA.ttf'),
        Varukers: require('./assets/fonts/VarukersPersonalUse-K70Be.ttf'),
      });
    } catch (err) {
      console.error(err, err.stack);
    } finally {
      setAppIsReady(true);
    }
  };

  const loadHomeScreen = async () => {
    if (appIsReady) {
      await SplashScreen.hideAsync();
    }
  };

  useEffect(() => {
    if (fontsLoaded) {
      setAppIsReady(true);
    }
  }, [fontsLoaded]);

  useEffect(() => {
    if (appIsReady) {
      loadHomeScreen();
    }
  }, [appIsReady])

  if (!fontsLoaded) {
    return null;
  }

  return (
    <SafeAreaProvider>
      <DbContextProvider>
        <LocaleContextProvider>
          <IntlProvider messages={messages[currentLang]} locale={deviceLocale} defaultLocale="en">
            <NavigationContainer>
              <Stack.Navigator>
                {!isAuthNd
                  ? (
                    <Stack.Screen
                      name="Login"
                      component={Login}
                      options={{
                        ...screenOptions,
                        headerShown: false,
                      }}
                    />
                  )
                  : (
                    <Stack.Screen
                      name="Home"
                      component={Home}
                      options={{
                        ...screenOptions,
                        headerShown: false,
                      }}
                    />
                  )
                }
                <Stack.Screen
                  name="Home"
                  component={Home}
                  options={{
                    ...screenOptions,
                    headerShown: false,
                  }}
                />
                <Stack.Screen
                  name="Flashcards"
                  component={Flashcards}
                  options={{
                    ...subScreenOptions,
                    headerShown: false,
                  }}
                />
                <Stack.Screen
                  name="StudyMode"
                  component={StudyMode}
                  options={{
                    ...subScreenOptions,
                    headerShown: false,
                  }}
                />
                <Stack.Screen
                  name="Texts"
                  component={Texts}
                  options={{
                    ...subScreenOptions,
                    headerShown: false,
                  }}
                />
                <Stack.Screen
                  name="Translator"
                  component={Translator}
                  options={{
                    ...subScreenOptions,
                    headerShown: false,
                  }}
                />
                <Stack.Screen
                  name="Dictionary"
                  component={Dictionary}
                  options={{
                    ...screenOptions,
                    headerShown: false,
                  }}
                />
                <Stack.Screen
                  name="Chat"
                  component={Chat}
                  options={{
                    ...subScreenOptions,
                    headerShown: false,
                  }}
                />
              </Stack.Navigator>
            </NavigationContainer>
          </IntlProvider>
        </LocaleContextProvider>
      </DbContextProvider>
    </SafeAreaProvider>
  );
}

Using

  "dependencies": {
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/native-stack": "^6.9.12",
    "@react-oauth/google": "^0.11.0",
    "axios": "^1.4.0",
    "expo": "~48.0.15",
    "expo-application": "~5.1.1",
    "expo-asset": "~8.9.1",
    "expo-auth-session": "~4.0.3",
    "expo-crypto": "~12.2.1",
    "expo-file-system": "~15.2.2",
    "expo-font": "^11.1.1",
    "expo-image": "~1.0.1",
    "expo-splash-screen": "~0.18.2",
    "expo-sqlite": "~11.1.1",
    "expo-web-browser": "~12.1.1",
    "isaac": "^0.0.5",
    "react": "18.2.0",
    "react-intl": "^6.4.4",
    "react-native": "0.71.8",
    "react-native-bcrypt": "^2.4.0",
    "react-native-crypto": "^2.2.0",
    "react-native-safe-area-context": "4.5.0",
    "uuid": "^9.0.0",
    "expo-system-ui": "~2.2.1"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0"
  },

Thank you so much for your help!

0

There are 0 answers