CS 466 - React Native Navigation

Due: 2019-11-08

Goals

  • Get more experience putting together an app in React Native
  • Learn about react-navigation
  • Learn to implement a Tab and Stack navigators
  • Learn to compose navigation styles

Prerequisites

  1. Accept the assignment on our Github Classroom.
  2. Download the git repository to your local computer.
  3. Update the package.json file with your name and email address.
  4. Install the package dependencies with npm install

Note that this is a bare bones React Native project made with expo init and the "blank" template. I did this purely to make working with GitHub Classroom easier. As a secondary note, another option is to use the "tabs" template which will start you off with a tab bar and multiple screens, but we are going to build ours up to understand the process.

Assignment

In this practical, we will start work on a project that we will carry through a couple of assignments: we are going to build the 'Ghost Hunter' app (or at least a version of it).

The main interface will use a tabbed, linear navigation scheme, with four pages. The first will be the interface for "catching" ghosts. The second will be a list of captured ghosts, the third will provide some statistics, and the last will contain the user profile.

The captured ghost list will use a stacked navigation scheme to show a list of the captured ghosts as well as the details on a separate page.

For this practical, we are only concerned with the navigation.

Part 1: Installing requirements

While the core React native has a number of UI widgets built into it, navigation tools are not included. So, we need to turn to secondary libraries. There are a couple of choices, but we are going to use react-navigation, as it is the most widely used and it supported by Expo.

To use this, we are going to install a collection of libraries with the following call:

expo install react-navigation react-native-gesture-handler react-native-reanimated react-native-screens react-navigation-stack react-navigation-tabs

Note that this uses expo install, NOT npm install --save. Behind the scenes, this will actually call npm install --save for you, however, you need to let expo handle the installation so that it can pick the right versions for you. There is actually a poor interaction between expo and the latest version of react-native-gesture-handler, so it really matters in this instance.

Part 2: Creating Screens

We will start by creating simple stubs for the different pages in our app. Make a new directory called screens at the root directory of your project. Inside, create a new JavaScript file called RadarScreen.js. This will be the screen the user uses to capture ghosts. Inside, make a simple component called RadarScreen which returns a View and a simple Text component inside that just says the name of the page. You can use the default App component as a model.

Once you have done this, duplicate that file to create screen components for GhostsScreen, GhostDetailScreen, ProfileScreen, and StatsScreen. Use these names both for the names of the files and for the components stored inside. You should also remember to update the text to reflect the correct screen.

Part 3: Creating the Tab Navigator

Create a new directory called navigation in the root directory of your project. Inside, create a new file called MainTabNavigator.js.

At the top of the file, add the following imports:

import React from 'react';
import { createBottomTabNavigator } from 'react-navigation-tabs';

The createBottomTabNavigator is a function that creates the navigator for us, we just need to pass in some configuration information.

Adding our screens

Right below those imports, import your four screens. These should all have the form:

import RadarScreen from '../screens/RadarScreen';

Now we are ready to make our tab navigator:

const tabNavigator = createBottomTabNavigator({
    Radar: RadarScreen,
    Ghosts: GhostsScreen,
    Stats: StatsScreen,
    Profile: ProfileScreen,
  });

  tabNavigator.path = '';
  
  export default tabNavigator;

This sets up a navigator with four tabs: 'Radar', 'Ghosts', 'Stats', and 'Profile'. These are the names of the "routes" in the app, and they will also serve as the default labels on the tabs.

Adding an App Container

In order to manage the navigation, react-navigation needs you to wrap all of the navigation components in a top level component called an "app container". This container manages the navigation state of your app as well as providing a component we can put into our root component.

Create a new file in navigation called AppNavigator.js. Put the following into the file:

import { createAppContainer } from 'react-navigation';
import MainTabNavigator from './MainTabNavigator';

export default createAppContainer(MainTabNavigator);

This create the new app container and installs our new tab navigator as the top-level navigator.

Now we need to install this into our root component. Open up App.js. Add the following to the top:

import { useScreens } from 'react-native-screens';

import AppNavigator from './navigation/AppNavigator';

useScreens();

We add the useScreens() call to make sure we are using native navigation components. The more important line is the one that imports the AppNavigator, which is the app container that we just created.

Our AppNavigator can be used like a normal React component. Remove the Text component and add in the AppNavigator in its place (note that it has no contents, so we can use the single tag format: <AppNavigator />).

The other change you should make to App.js is to remove the alignment and justification styling from the container style. The app container would like to be in a View with flex:1, but it wants the default layout.

Fire up your app -- there is finally something to see. You should get a tab bar along the bottom of the app, and tapping the different tabs should take you to the different screens.

Adding icons

Of course, we are used to seeing icons down on the tab bar, so let's add some.

Expo provides a large number of vector icons that we can use. To use them, we need to do another installation. Stop your server and run:

expo install @expo/vector-icons

As you look through the directory, you will see that each icon comes with a name (e.g., stepforward) and a component that provides it (e.g., AntDesign) (the component is based on the original source of the icon).

To use one of these icons, we would need to import the component

import { AntDesign } from '@expo/vector-icons';

and then create the icon component

<AntDesign name={stepforward} size={25} color='black' />;

To add in our icons, we are going to add a second argument to our call to createBottomTabNavigator in MainTabNavigator.js. The second argument allows us to add options, such as the default navigation options. We are going to pass in a function that returns another function that will be responsible for adding our icon components.

The documentation provides an example of what this should look like. Follow this model, using the icon directory to find appropriate icons for your tabs.

I will recommend, however, using a switch statement rather than an if since we have four routes. Something like

let IconComponent;
let iconName;
switch (routeName){
  case 'Radar':
      iconName = 'some icon name';
      IconComponent = some component;
      break;
  etc...
}

Try your app again -- it should have nice icons for each tab now.

Part 4: Connecting to the ghost details screen

Hopefully, you have noticed that we did not include the GhostDetailScreen in the tab navigator. The idea is that we want to view the list of ghosts on the GhostsScreen and then navigate to the GhostDetailScreen for more information.

We are going to use the hierarchical model here (stacking up views and providing reverse navigation through the 'Back' button). To do this, we are going to write a second navigator.

Create a new file in the navigation directory called GhostStackNavigator.js.

It will look like this:

import { createStackNavigator } from 'react-navigation-stack';

import GhostsScreen from '../screens/GhostsScreen';
import GhostDetailScreen from '../screens/GhostDetailScreen';

const GhostStackNavigator = createStackNavigator({
    Ghosts: GhostsScreen,
    GhostDetails: GhostDetailScreen,
},
{
    initialRouteName: 'Ghosts'
});

export default GhostStackNavigator;

Note that this looks very similar to our tab navigator. This time, we are creating a stack navigator, but otherwise, the structure is the same. We have a function that creates the navigator (createStackNavigator). The first argument sets up the routes. The second sets some options for the navigator. In this instance, we are setting the start page.

We would like to be able to move between the list and the details while we are looking at the 'Ghosts' route of the tab navigator. Fortunately, it is very easy to compose multiple navigators together. Go to the MainTabNavigation.js file and swap out GhostsScreen for GhostStackNavigator (you will need to fix the imports as well). That's it. Think of it like replacing a leaf node on a tree with another tree.

Now we need to provide a way to get from our 'Ghosts' route to our 'GhostDetails' and back.

In the GhostsScreen component, add a Button and give it a title of 'Details' (or anything else you like -- this is purely for our benefit).

The trickier part is getting it to actually navigate to another screen.

One of the things that our navigators do is pass along a prop called navigation to their child components. This navigation object includes a function called navigate. We can call this function with the name of our destination.

So, make sure that you have added the props argument to GhostsScreen and then add an onPress prop to the Button: onPress={()=>props.navigation.navigate('GhostDetails')}.

When you run your app, you should be able to press the button and see the screen transition to the detail page.

If you look at the top of the page, you will see that the stack navigator has added the 'Back' button so you can return to the earlier screen.

For more details on this, consult the documentation.

Part 5: Refining the stack navigator

For the tab navigator, we added in icons. For our stack navigator, we will add titles to the pages.

When we added icons to the tabs, we did it within the tab navigator itself using the default navigator options. For our stack navigator, we will configure the pages individually. The two methods are interchangeable and we will have different reasons for preferring one over the other. For example, for the tabs, it made sense to handle the icons all in the same place, and it made it easier to swap out a page.

Adding titles, however, is pretty simple and there is no reason to hide the detail from the screen components.

All we need to do is add a navigationOptions property to the screen component. Since we are using function components, we will add this after the function definition.

GhostsScreen.navigationOptions = {
  title: 'Ghosts'
};

Everything in JavaScript is an object, so we can always add properties on to anything, even functions.

Do this for both GhostsScreen and GhostDetailScreen.

Run your app again. The screen should now have the title set in the navigation bar at the top. On iOS, the label on the 'Back' link has also changed to provide the breadcrumb back to the previous screen.

Finishing up

Commit your changes to git and push them back up to GitHub. I will find them there.

Grading

Points Requirement
✓/✗ Create the five screens
✓/✗ The tab bar navigates between the top four screens
✓/✗ The tab bar has appropriate icons
✓/✗ The 'Ghost' tab has an embedded stack navigator
✓/✗ The stacked screens have titles set