/ android

React Native - Creating a navigation toolbar for Android

Software Versions

React15.2.1
react-native0.31.0
OSAndroid
# TL;DR – Here is the [Code](https://github.com/codingmiles/react-native-snippets/tree/master/ToolbarNav "Code Link")

Demo

react-native-toolbar-nav

Pre-Requisite

  • React native is installed
  • You are able to run the sample app either on emulator or actual device. Follow the Getting Started guide at react documentation.
  • ES6 basics

Setting Up

Run following command in the terminal to setup the  ToolbarNav app.

react-native init ToolbarNav

Start your emulator or connect to the device. cd to  ToolbarNav directory and run

react-native run-android

You should have the default react-native app running on your screen

default-react-native-app

Set up navigation

Let’s start by cleaning up the code. Replace your index.android.js code with following. This will render empty screen on the screen.

import React, { Component } from 'react';
import {
	AppRegistry,
	View
} from 'react-native';

class ToolbarNav extends Component {
	render() {
			return (
				<View></View>
			); 
		} 
	} 
}

AppRegistry.registerComponent('ToolbarNav', () => ToolbarNav);

Navigation is the most important part of any app. React-native provides a Navigator component just for this purpose.

Our app will have two screens we will navigate between. One is the  Home screen which is also the default screen and the other is  Settings screen.  Navigator takes a property called as  initialRoute

<Navigator initialRoute={{ id: 'home' }} />

initialRoute takes a route. In this case, we define our route as a JavaScript object with id as home. This defines the default route to be loaded on app launch.

Navigator also takes another property called as  renderScenerenderScene is a function which decides which screen (or scene) to load for a given route.

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View,
  Navigator
} from 'react-native';

class ToolbarNav extends Component {

  _renderScene (route, navigator) {
    switch (route.id) {
      case 'home':
        return (
          <Home navigator={navigator}/>
        )
      case 'settings':
        return (
          <Text>Settings</Text>
        )
    }
  }

  render() {
    return (
      <Navigator
        initialRoute={{ id: 'home' }}
        renderScene={(route, navigator) => this._renderScene(route, navigator)}
      />
    );
  }
}

AppRegistry.registerComponent('ToolbarNav', () => ToolbarNav);

In the above code, renderScene receives two arguments, route and navigator. Once the app is loaded, route is set to initialRoute. So route.id in this case is home. Go ahead and run the above code. You should see a screen with text Home at the top. Make sure you have imported Navigator and Text from react-native. Note that we have also defined a variable _navigator. This is to keep the reference of navigator object for future use.

Once the initial route is set up, we can use  navigator.push(route) to navigate to other screen or  navigator.pop() to go back to previous screen.

For example, to navigate to settings screen from home screen, we can use:

navigator.push({ id: 'settings' });

This will call renderScene function that we configured earlier and since the id is settings, it will render the settings screen. We will use this in a bit.

Similarly, calling  navigator.pop() from the settings screen will take us back to previous scene which is home in this case.

There are other useful properties which are available as part of Navigator. You can also use Navigator.NavigationBar to setup a navigation bar at the top.

Create Home Screen

Before we can navigate to Settings screen, we need the home screen ready. Create a directory called components under ToolbarNav folder. Within this, let’s create a file called Home.js. Our home screen will look like:

home

First, let’s set up the toolbar using a component called ToolbarAndroid. In our toolbar, we have an app logo and settings icon. Download these images from here. Create a new folder called images under ToolbarNav and copy these images to this folder.

We start by rendering the ToolbarAndroid component in Home.js with just the logo and title. Add following in  Home.js

import React, { Component } from 'react';
import {
	StyleSheet,
	ToolbarAndroid
} from 'react-native';

export default class Home extends Component {
	render() {
		return (
			<ToolbarAndroid
				title='Home'
				logo={require('../images/logo.png')}
        style={styles.toolbar}
        titleColor='white'
      />
		)
	}
}

var styles = StyleSheet.create({
	toolbar: {
		height: 56,
		backgroundColor: '#4883da',
	},
});

Before we can run the code again, we need to import Home.js in our index.android.js file. It is still rendering a Text component. Update your index.android.js to following:

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View,
  Navigator
} from 'react-native';

import Home from './components/Home';

class ToolbarNav extends Component {

  _renderScene (route, navigator) {
    switch (route.id) {
      case 'home':
        return (
          <Home navigator={navigator}/>
        )
      case 'settings':
        return (
          <Text>Settings</Text>
        )
    }
  }

  render() {
    return (
      <Navigator
        initialRoute={{ id: 'home' }}
        renderScene={(route, navigator) => this._renderScene(route, navigator)}
      />
    );
  }
}

AppRegistry.registerComponent('ToolbarNav', () => ToolbarNav);

Note that we are also passing a prop called navigator to Home component. We will use navigator to navigate to Settings. Run the code again and you should see following on screen:

logo-title

Now, we will add the settings icon to the toolbar by providing actions to the ToolbarAndroid component.

import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  ToolbarAndroid,
  TouchableOpacity
} from 'react-native';

export default class Home extends Component {

	_navigateToSettings () {
		this.props.navigator.push({
			id: 'settings'
		});
	}

	_onActionSelected () {
		this._navigateToSettings()
	}

	render () {
		return (
			<View style={styles.parentContainer}>
				<ToolbarAndroid
					title='Home'
					logo={require('../images/logo.png')}
          actions={toolbarActions}
          style={styles.toolbar}
          titleColor='white'
          onActionSelected={() => this._onActionSelected()}
        />
    </View>
		)
	}
}

var toolbarActions = [
	{title: 'Settings', icon: require('../images/settings.png'), show: 'always'},
];

var styles = StyleSheet.create({
	parentContainer: {
		flex: 1,
	},
	toolbar: {
		height: 56,
		backgroundColor: '#4883da',
	},
});

toolbarActions is an array of actions that can be placed on the toolbar. ToolbarAndroid also takes a prop called onActionSelected. This is a function which will be called whenever an action is pressed on the toolbar. Since we want to navigate to Settings screen on press of settings button, we will use the navigator object passed to Home.js and push the Settings tab as highlighted in line numbers 13-20. We are passing id as settings and this will call renderScene function that we have in index.android.js.

Navigator’s push and pop works like a stack. You can push as many number of scenes on the the stack and pop will remove the last screen and show the previously pushed screen.

Try running the code now. Click on the settings icon and you should see a page with text Settings. This is because we have setup renderScene to show a text when the id is settings. We will set this up to use another component similar to Home.

But there is a problem here. We can go forward to Settings screen but we can’t go back to home. For one thing, we don’t have a button to go back. But we can’t go back even by pressing the hardware back button (or the back button in the bottom panel) on the device. Let’s wire up android’s hardware back button to our app.

In index.app.js add following lines after var _navigator;

BackAndroid.addEventListener('hardwareBackPress', () => {
  if (_navigator && _navigator.getCurrentRoutes().length > 1) {
    _navigator.pop();
    return true;
  }
  return false;
});

We had saved the reference of navigator in a variable _navigator for this purpose. If there are more than one screens on the stack, we pop the screen if back button is pressed. Make sure to import BackAndroid from react-native. Now you should be able to use the hardware back button from the settings screen to go back to Home screen.

Before we head to Settings screen, we will add a button in the Home screen which will also navigate to Settings screen. Update your Home.js with:

import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  ToolbarAndroid,
  TouchableOpacity
} from 'react-native';

export default class Home extends Component {

	_navigateToSettings () {
		this.props.navigator.push({
			id: 'settings'
		});
	}

	_onActionSelected () {
		this._navigateToSettings()
	}

	render () {
		return (
			<View style={styles.parentContainer}>
				<ToolbarAndroid
					title='Home'
					logo={require('../images/logo.png')}
          actions={toolbarActions}
          style={styles.toolbar}
          titleColor='white'
          onActionSelected={() => this._onActionSelected()}
        />
        <View style={styles.container}>
					<TouchableOpacity
						style={styles.button}
						onPress={() => {
	            this._navigateToSettings()
	        	}  
	        }>
	        	<Text style={styles.buttonText}>Go to Settings</Text>
	        </TouchableOpacity>
	      </View>
			</View>
		)
	}
}

var toolbarActions = [
	{title: 'Settings', icon: require('../images/settings.png'), show: 'always'},
];

var styles = StyleSheet.create({
	parentContainer: {
		flex: 1,
	},
	toolbar: {
		height: 56,
		backgroundColor: '#4883da',
	},
});

On click of the button, _navigateToSettings is called again. Our home screen should look like:

home

Our app is set up with everything except for Settings screen.

Create Settings Screen

Settings screen is very similar to Home screen with some minor changes to add a back button in the toolbar and to remove logo and settings icon. You will need to copy the back icon from here.

Add a new file called Settings.js under ToolbarNav/components and add following code:

import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  ToolbarAndroid,
  TouchableOpacity
} from 'react-native';

var _navigator;

export default class Settings extends Component {

	render () {
		_navigator = this.props.navigator;
		return (
			<View style={styles.parentContainer}>
				<ToolbarAndroid
					title='Settings'
					navIcon={require('../images/back_arrow.png')}
					onIconClicked={() => this.props.navigator.pop()}
          style={styles.toolbar}
          titleColor='white'
        />
				<View style={styles.container}>
					<TouchableOpacity
						style={styles.button}
						onPress={() => this.props.navigator.pop()}  
	        >
	        	<Text style={styles.buttonText}>Back to home</Text>
	        </TouchableOpacity>
	      </View>
			</View>
		)
	}
}

var styles = StyleSheet.create({
	parentContainer: {
		flex: 1,
	},
	container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  toolbar: {
  	height: 56,
    backgroundColor: '#4883da',
  },
  buttonText: {
    fontSize: 18,
    color: 'white',
    alignSelf: 'center'
  },
  button: {
    height: 44,
    width: 200,
    backgroundColor: '#4883da',
    alignSelf: 'center',
    justifyContent: 'center'
  }
});

The only difference here is the removal of logo and toolbarActions from the ToolbarAndroid and addition of back navIcon along with onIconClicked. onIconClicked is just used to pop the screen from the navigation stack.

Before we run the code, let’s import the Settings component in the index.android.js file and use that in the renderScene instead of Text component. Complete index.android.js code is:

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Navigator,
  BackAndroid
} from 'react-native';

import Home from './components/Home';
import Settings from './components/Settings';

var _navigator;

/* 
  Android hardware back button. Without this, android device back button prese
  will close the app directly if you are on settings screeb without going back to
  home.
 */
BackAndroid.addEventListener('hardwareBackPress', () => {
  if (_navigator && _navigator.getCurrentRoutes().length > 1) {
    _navigator.pop();
    return true;
  }
  return false;
});

class ToolbarNav extends Component {

  _renderScene (route, navigator) {
    _navigator = navigator;
    switch (route.id) {
      case 'home':
        return (
          <Home navigator={navigator}/>
        )
      case 'settings':
        return (
          <Settings navigator={navigator} />
        )
    }
  }

  render() {
    return (
      <Navigator
        initialRoute={{ id: 'home' }}
        renderScene={(route, navigator) => this._renderScene(route, navigator)}
      />
    );
  }
}

AppRegistry.registerComponent('ToolbarNav', () => ToolbarNav);

Run the code and we should have our settings screen available as:

settings