Mega Bundle Sale is ON! Get ALL of our React Native codebases at 95% OFF discount 🔥

In this tutorial, we are taking a look at how to detect and support dark mode in React Native apps. You are going to build a small demo app that sets its appearance based on the platform OS. The platform OS will have two theme modes, dark or light. By default, when the app will start, it is going to have the theme based on the platform OS but the user is going have an option to toggle between the themes. react native dark mode

Configure react-native-appearance

To start, let us create a new React Native project by executing the following command and install the required dependencies to build this app.

# create a new project
react-native init rnDarkModeStyledComponentsDemo

# navigate inside the project directory
cd rnDarkModeStyledComponentsDemo

# install the following dependencies
yarn add styled-components react-native-appearance

iOS developers have to install pods to complete the configuration for react-native-appearance by running the following commands:

cd ios/
pod install

Android developers have to add the following configuration. First, open the file android/app/src/main/AndroidManifest.xml and add the uiMode flag.

android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode">

Next, open android/app/src/main/java/com/rnDarkModeStyledComponentsDemo/MainActivity.java and add the following.

import android.content.Intent;
import android.content.res.Configuration;

// inside public class MainActivity extends ReactActivity

@Override
 public void onConfigurationChanged(Configuration newConfig) {
 super.onConfigurationChanged(newConfig);
 Intent intent = new Intent("onConfigurationChanged");
 intent.putExtra("newConfig", newConfig);
 sendBroadcast(intent);
 }

Define Themes

In this section, let us define two basic themes in two separate files. Create a new directory called src/themes/ and inside it create two new files: light.js and dark.js. Inside light.js, let us define a basic set of colors to be used when for different attributes when this theme is active. Add the following snippet and do not forget to export it.

const light = {
  theme: {
    background: '#ededed',
    border: '#bdbdbd',
    backgroundAlt: '#eaeaeb',
    borderAlt: '#bdbdbd',
    text: '#171717'
  }
}

export default light

Next, open dark.js file and similarly, add the theme values for it.

const dark = {
  theme: {
    background: '#2E3440',
    border: '#575c66',
    backgroundAlt: '#575c66',
    borderAlt: '#2E3440',
    text: '#ECEFF4'
  }
}

export default dark

Defining a Theme Manager using context

In this section, let us define a theme context manager that will switch or toggle between the two themes based on the value of the theme provided. The user is going to have access to a switch button that they can use to toggle between the two themes. Apart from that, using the module react-native-appearance, this react native app is going to access the operating system’s appearance too. To start, import the following statements. Using styled-components has the benefit that it supports theming to the full extent by providing <ThemeProvider> to wrap the context component.

import React, { createContext, useState, useEffect } from 'react'
import { StatusBar } from 'react-native'
import { ThemeProvider } from 'styled-components/native'
import { Appearance, AppearanceProvider } from 'react-native-appearance'
import lightTheme from './light'
import darkTheme from './dark'

Next, create a ThemeContext that is going to hold the value of the current theme (or mode) and a helper function to change that value.

const ThemeContext = createContext({
  mode: defaultMode,
  setMode: mode => console.log(mode)
})

Then, useTheme is going to be a helper function that uses the previously created ThemeContext. Do not forget to export it, since you will be using it directly in the UI component later.

export const useTheme = () => React.useContext(ThemeContext)

Let us define a Theme Manager Provider that is going to take care of setting the theme, changing the state or mode of the current theme to the next and using useEffect hook, it is going to listen to the theme changes made by the operating system. This listening is done by adding a subscription using addChangeListener from react-native-appearance. Lastly, wrap children of the component inside the ThemeProvider imported from styled-components/native. The children here are going to be the StatusBar component from react-native as well as the other UI components passed as children itself.

const ManageThemeProvider = ({ children }) => {
  const [themeState, setThemeState] = useState(defaultMode)

  const setMode = mode => {
    setThemeState(mode)
  }

  useEffect(() => {
    const subscription = Appearance.addChangeListener(({ colorScheme }) => {
      setThemeState(colorScheme)
    })
    return () => subscription.remove()
  }, [])

  return (
    <ThemeContext.Provider value={{ mode: themeState, setMode }}>
      <ThemeProvider
        theme={themeState === 'dark' ? darkTheme.theme : lightTheme.theme}>
        <>
          <StatusBar
            barStyle={themeState === 'dark' ? 'light-content' : 'dark-content'}
          />
          {children}
        </>
      </ThemeProvider>
    </ThemeContext.Provider>
  )
}

Lastly, the root of the app has to be wrapped inside the AppearanceProvider to make the OS changes work and listen to OS subscriptions.

const ThemeManager = ({ children }) => (
  <AppearanceProvider>
    <ManageThemeProvider>{children}</ManageThemeProvider>
  </AppearanceProvider>
)

export default ThemeManager

Using ThemeManager inside the App

To use the ThemeManager to listen to theme mode changes or at the same time, allow the user to make changes to the appearance of the app, manually by toggling, start by importing the following statements inside App.js. To let the user toggle between two themes, let us the Switch component from react-native.

import React from 'react'
import styled from 'styled-components/native'
import ThemeManager, { useTheme } from './src/themes/ThemeContext'
import { Switch } from 'react-native'

Now create a HomeScreen component that is going to have the Switch component wrapped inside Container created using styled-components. Using props from styled-components, the background of the theme can be easily switched.

const HomeScreen = () => {
  const theme = useTheme()
  return (
    <Container>
      <Switch
        value={theme.mode === 'dark'}
        onValueChange={value => theme.setMode(value ? 'dark' : 'light')}
      />
    </Container>
  )
}

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
  background: ${props => props.theme.background};
`

Lastly, wrap the HomeScreen component inside ThemeManager to make it work.

const App = () => (
  <ThemeManager>
    <HomeScreen />
  </ThemeManager>
)

export default App

Testing Dark Mode in React Native

I am going to test this app inside an iOS simulator. By default, the iOS simulator I am running has a light theme or mode. To find where you can switch between appearances on an iOS simulator, open Settings and then you are going to come across a Developer menu as shown below. ss1 Open that, and you are going to come across the first thing Appearance. By default, you can see it is set to light mode. ss2 Go back to the terminal window, build the app by using the command react-native run-ios, if you haven’t and when the app opens for the first time, it will show the lighter background and StatusBar component is dark in color. ss3 Here is the complete demo when OS appearance setting changes, it directly reflects in our React Native app. ss4  

Conclusion

As you can see, adding dark mode support in React Native apps is pretty straightforward when using react-native-appearance npm package. The nice thing is that this works for both iOS and Android devices that support dark mode.

Next Steps


Leave a Reply

Your email address will not be published. Required fields are marked *

Shopping Cart