How to use React Native StatusBar theme for different screens
So it’s been 2 years I am exploring things on React Native. I am pretty much impressed by this framework as it is much faster and reliable compared to other cross-platform frameworks like Cordova, Xamarin, Ionic, etc.
Recently I got a chance to work on a large scale project where we have to change the status bar theme based on current screen header. As we know React Native supports 2 types of theme for StatusBar.
- dark-content
- light-content
We have a StatusBar component using which we can set a theme for iOS and Android.
<StatusBar barStyle=”light-content” />
We can either put it in our main index.js
or App.js
file and then we can initialize our navigation component. So now our application will have light-content
status bar which will show status bar content in white color. It will look something like this :
Now suppose we have a few screens where we have to show dark-content
status bar, may be in a screen where header color is white or very light at that time light-content
will not work as it will be hard for the user to see status bar with white text on a light background.
For this purpose we have Imperative API, we can use StatusBar object to set its theme and background color(in case of Android only).
For cases where using a component is not ideal, there is also an imperative API exposed as static functions on the component. It is however not recommended to use the static API and the component for the same prop because any value set by the static API will get overriden by the one set by the component in the next render.
This is from the official document, but still, we can use static methods to set theme and background color or hide the status bar.
List of methods available in StatusBar
- setHidden
- setBarStyle
- setBackgroundColor
- setNetworkActivityIndicatorVisible
- setTranslucent
So coming back to the problem where we need different status bar theme for different screen, we can use Imperative API and by using static methods we can set status bar theme in componentWillMount
or render
. In this case, each of your js files will be having this code :
StatusBar.setBarStyle('dark-content')
//or
StatusBar.setBarStyle('light-content')
Now assume we will have 2 screens names screen1 and screen2. Screen1 is having dark theme and screen2 is having light theme. Now when user moves from screen1 to screen2, it will change status bar to light theme. Now when user presses back button and moves to screen1 it should have dark theme, in this case it will be light theme only because we have written code in componentWillMount
or constructor
so neither of the methods will be called.
To solve this issue we can use listener of react-navigation onNavigationStateChange
, as the name suggests it will be called whenever navigation state changed. So our code will look like :
function getActiveRoute(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getActiveRoute(route);
}
return route;
}const AppNavigator = createStackNavigator(AppRouteConfigs);
const AppContainer = createAppContainer(AppNavigator);export default () => (
<AppContainer
onNavigationStateChange={(prevState, currentState, action) => {
const currentScreen = getActiveRoute(currentState);
const prevScreen = getActiveRoute(prevState);
if (prevScreen.routeName !== currentScreen.routeName) {
if(currentScreen.routeName === 'Screen1')
StatusBar.setBarStyle('dark-content')
else
StatusBar.setBarStyle('light-content')
}
}}
/>
);
It worked perfectly but here also one case arises. It is pretty much good solution in the case where we have a small project or less number of screens. But what if we are having 50–60 or more number of screens, we cannot put if…else condition of switch case to match routeName and then have a theme based on names. Or suppose we have tabbar, it will not be refreshed when user switches from one tab to another. In that case status bar theme will not change.
If we will talk about tabbar, react-native team has given a solution to listen a callback when user switches from multiple tabs. So when that is focused, we will get an event for that and there we can set our status bar theme.
componentDidMount() {
this._navListener = this.props.navigation.addListener('didFocus', () => {
StatusBar.setBarStyle('light-content');
//or
StatusBar.setBarStyle('dark-content')
});
}componentWillUnmount() {
this._navListener.remove();
}
It worked like a charm, but again there is a question like are we going to write this listener in all of our screens? Answer is NO, then what should we do?
To solve this issue we have found a prop that we can use with react-navigation, we can use params
to pass default parameters from our route. It will look something like :
Screen1:{ screen:Screen1, params:{data:'Extra Data'} }
Note : params
is supported from react-navigation version 3.X
That’s it, from here we got the way to pass theme name and get that name on our navigation state listener. So the final code will be like :
Screen1:{ screen:Screen1, params:{statusbar:’dark-content’} }
Screen2:{ screen:Screen2, params:{statusbar:'light-content'} }
and your listener will be
function getActiveRoute(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getActiveRoute(route);
}
return route;
}
const AppNavigator = createStackNavigator(AppRouteConfigs);
const AppContainer = createAppContainer(AppNavigator);
export default () => (
<AppContainer
onNavigationStateChange={(prevState, currentState, action) => {
const currentScreen = getActiveRoute(currentState);
const prevScreen = getActiveRoute(prevState);
if (prevScreen.routeName !== currentScreen.routeName) {
const statusTheme = currentScreen.params.statusbar;
StatusBar.setBarStyle(statusTheme)
}
}}
/>
);
We have around 60 screens in our project, out of 60 around 20 screens are having light-content
as status bar theme, then this is a preferred way to go with(as per my opinion).
Still, I am not sure whether it is the right approach to solve the problem or not. If anyone has any better solution or workaround, feel free to mention it.