React Native

2.2 Layout & Navigation

Module Still Under Development

In this module we will look focus on routing & navigation (opens new window), or "moving between screens" in your mobile app. But first we should address an important layout issue.

# Flex Layout

When building a mobile app with React Native, you will, in most cases, be using a Flexbox layout.

When adding components to your UI, remember that the default width for <View> components is auto. This means that components can collapse to the size needed for their content. They are more like HTML inline elements than block elements.

You will build the structure of a page primarily with nested <View> components. The style property flex accepts a numeric value, which is used to create a ratio of sizes between all the sibling views. Let's take a basic layout that has two <View> components inside a parent <View>. In the styles object there will be three properties: parent, a, and b.

<View style={styles.parent}>
  <View style={styles.a}>
    <Text>A</Text>
  </View>
  <View style={styles.b}>
    <Text>B</Text>
  </View>
</View>;

const styles = StyleSheet.create({
  parent: {
    flex: 1,
  },
  a: {
    flex: 1,
  },
  b: {
    flex: 1,
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

With all three of these style objects having set flex: 1, it means that the parent will take 1/1 of the height. The default flexDirection of a <View> is column. The alternative is row. If the parent had flex: 0.5, it would be half the height of the screen. If the parent had flex: 2 it would be twice the height of the screen. HOWEVER, a <View> does not scroll by default. So, any content past the bottom of the screen will not be visible.

The two children <View> components have the same value so they get an equal proportion of the parent's main axis (column).

Note: A <ScrollView> will make the children automatically collapse to the height needed by their content. The flex proportions will be ignored.

The justifyContent property has a default of flex-start. So the elements will start at the top and only fill as much space as their content requires. Other values are flex-end, space-evenly, space-around, space-between and center.

The alignItems property has a default of value of stretch meaning that they will fill the width (in a column). Other values are, like CSS, flex-start, flex-end, center, and baseline.

The flexGrow and flexShrink default values are both zero. So, it is the content that decides the height of each child in a column.

You can always nest <View>s inside <View>s and you can change the flexDirection between row and column at each level.

# Safe Area Context

The core React Native project provides a <SafeAreaView /> component to help keep you application content from being obscured by sensor notches or status bars. However, it only works with iOS 11+. Fortunately, there is a community developed package that provides consistent support across all iOS, Android, and Web target deployments. It is called react-native-safe-area-context (opens new window) and Expo provides an optimized installer ...

npx expo install react-native-safe-area-context
1

As its name implies this module uses React's Context API and, you will need to wrap your top App level JSX component with the <SafeAreaProvider> component imported from react-native-safe-area-context. Then wrap each of your view (screen) components with the <SafeAreaView> component imported from react-native-safe-area-context.

# With React Navigation Header

When using the <SafeAreaView> component with React Navigation's default header component, you will see extra unwanted padding below the header. To correct this, set the edges prop on the <SafeAreaView> component to exclude the top edge of the device. Just list the sides that you want to go all the way to the edge of the screen.

The <SafeAreaProvider> needs to be at the top level of your app, not on every screen.

import { SafeAreaView, SafeAreaProvider } from 'react-native-safe-area-context';

export default function Demo() {
  return (
    <SafeAreaProvider>
      <SafeAreaView style={{ flex: 1 }} edges={['right', 'bottom', 'left']}>
        {/* rest of your JSX content */}
      </SafeAreaView>
    </SafeAreaProvider>
  );
}
1
2
3
4
5
6
7
8
9
10
11

# Routing & Navigation

React Navigation (opens new window) is the most popular navigation library in the React Native ecosystem and it is maintained by Expo, so it's guaranteed to work great in your Expo managed apps. Both React Navigation and Expo Router are Expo frameworks for routing and navigation. Expo Router is a wrapper around React Navigation and many of the concepts are the same.

We will be starting with React Navigation. Here are the notes on reactnative.dev about Navigation (opens new window)

There are three common modes for mobile application navigation: stacks, tabs, and drawers. The React Navigation library supports all three of these modes and they can be used in combination (see nested navigation (opens new window)).

Note: There is another library alternative called React-Native-Navigation. You can use this if you used React Native CLI to create your project. However, React Navigation is installed along with Expo.

# Install dependencies

The React Navigation library is divided into several modules. A core module and separate modules for each routing mode. It also relies on some peer dependencies. Start by installing the core library with yarn or npm.

npm i @react-navigation/native @react-navigation/native-stack
1

Then add the peer dependencies through npx expo.

npx expo install react-native-screens react-native-safe-area-context
1

After this you need to add the module for the type of navigation you want to use in your app. For our lab demo, we will use the familiar stack navigation - this works similarly to how the history API works in the browser. The stack is like an array of recently viewed pages/components.

# Hello React Navigation

Lets build a very simple React Native app with Expo and React Navigation. To keep things simple, it will have just three pages and very little content.

# Initialize a new project

npx create-expo-app@latest hello-nav
1

Then add the React Navigation modules as per the instructions above.

# Create three simple page components

Create a folder called pages or screens. Create the three pages components in this folder.

// /pages/HomeScreen.js
import { View, Text } from 'react-native';

export default function HomeScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}
1
2
3
4
5
6
7
8
9
10
// /pages/AboutScreen.js
import { View, Text } from 'react-native';

export default function AboutScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>About Screen</Text>
    </View>
  );
}
1
2
3
4
5
6
7
8
9
10
// /pages/ContactScreen.js
import { View, Text } from 'react-native';

export default function ContactScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Contact Screen</Text>
    </View>
  );
}
1
2
3
4
5
6
7
8
9
10

# The NavigationContainer

Similar to the <BrowserRouter> component that we used from React Router, the <NavigationContainer> component provides the navigation state management context and usually wraps the main App component's JSX.

// /App.js
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return <NavigationContainer>{/* the stack navigation screens will be added here */}</NavigationContainer>;
}
1
2
3
4
5
6

Important note: all your initial top-level screens defined in the StackNavigator or your TabBottomNavigator will be loaded when your app loads.

# Create a stack navigator

To use the stack navigator import the createNativeStackNavigator function. When invoked, it returns a Stack object with two properties containing React components: Screen and Navigator.

The routes are defined by placing the Screen components as children to the Navigator component in your JSX markup.

Update the App.js component to look like this ...




 
 

 




 
 
 




// /App.js updated
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './pages/HomeScreen';

const Stack = createStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

OK, now add routes for the other two screens.

Solution: Try yourself before you look




 
 








 
 





import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './components/HomeScreen';
import AboutScreen from './components/AboutScreen';
import ContactScreen from './components/ContactScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="About" component={AboutScreen} />
        <Stack.Screen name="Contact" component={ContactScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Header Options

The route name, by default, is used as the page title that appears in the header. If you want to customize it, you can add an options prop.

<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Hello' }} />
1

You can also set the background style and text colour for the header using the headerStyle and headerTintColor props.




 
 
 
 
 


<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={{
    title: 'Hello',
    headerStyle: { backgroundColor: 'indigo' },
    headerTintColor: 'white',
  }}
/>
1
2
3
4
5
6
7
8
9

TIP

Remember to adjust the status bar text You can use the React Native StatusBar (opens new window) component's barStyle prop to adjust the system status text to light or dark or auto or inverted to fit your app's colour scheme. e.g.

<StatusBar barStyle="light" />
1

# Default Header Options

It is great to be able to change individual header display properties, but most of the time you will want a consistent look across your application. Rather than repeating the config options on every screen, you can set the default options to apply to all screens using the screenOptions prop on the Stack.Navigator component. e.g.

<Stack.Navigator
  screenOptions={{
    headerStyle: {backgroundColor: 'indigo'},
    headerTintColor: 'white'
  }}
  initialRouteName="Home"
>
1
2
3
4
5
6
7

Any default settings can be overridden by the options prop on an individual Stack.Screen component as needed.

The list of options that you can set for your Stack.Navigator or Stack.Screen are:

  • initialRouteName: only for Stack.Navigator but has the name of the default Route.
  • headerStyle: style object for the header.
  • headerTintColor: tint colour for the whole header.
  • title: a title to use for the header.
  • header: a custom header function that returns a component to use instead of the default header.
header: ({ navigation, route, options, back }) => {
  //it will be passed references to the navigation object, the route object, the options for the header, and
  // a back button object that includes { title: 'BACK' } text for the back button in the header
};
1
2
3
4
  • headerLeft: function that accepts props and returns component for the left side of the header.
  • headerRight: function that accepts props and returns component for the right side of the header.
  • headerTitleAlign: left or center.
  • headerTitle: Text for the title. Default is the name of the Route.

When writing the function for the headerLeft property you are building a custom component. There is, however, a built-in component that is used for the back button is called <HeaderBackButton>. It can be used along with a bunch of properties, which you can see here (opens new window).

<HeaderBackButton label="Hello" onPress={() => console.log('back pressed')} />
1

And you can read more about the header properties here (opens new window).

# How to move between screens

Each screen component receives two navigator related objects as props: navigation, and route. When you create your component function, which accepts props, you can destructure to get the two properties.

export default HomeScreen({navigation, route}) => {
  //navigation and route come from props

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}
1
2
3
4
5
6
7
8
9

Here is the Route Prop Reference (opens new window) and Navigation Prop Reference (opens new window).

In order to change screens you need to tell the navigation object which new screen to display using the navigation.navigate() method. This would typically be in an onPress event handler. The first argument to the navigate() method is the name (as a string) of a screen defined in the Stack.Navigator - in our example that is in the App component.

<Button title="Read more about us" onPress={() => navigation.navigate('About')} />
<Button title="Contact us" onPress={() => navigation.navigate('Contact')} />
1
2

# Going back

There are several options here.

  1. The header will automatically add a < Back button if the user is not on the first page of the stack.
  2. The hardware back button on Android devices is automatically configured to go back one step in the stack.
  3. You can programmatically pop the last screen off the stack with navigation.goBack() in an onPress handler. You can call this from any function that you create inside your component function.
  4. You can navigate directly to any earlier screen in the stack (skipping intermediate screens) by using the navigation.navigate() method. e.g.
<Button title="Home" onPress={() => navigation.navigate('Home')}>
1

There is also a navigation.popToTop() method that put the current Screen on the top of the Navigation Stack.

Here is a list of the Navigation Events (opens new window) with an example of how to add your own listener inside a useEffect Hook.

Here is a guide (opens new window) for adding the functionality of preventing the user from going back to the previous screen. For example, they were filling out a form and you want to confirm leaving the screen.

# Passing route params

If you need to pass parameters, like an object id, to a screen component then you can pass an object as the second argument with key:value pairs.

<Button title="Read book review" onPress={() => navigation.navigate('Review', {id: book.id})}>
1

Then in the screen component you can access these parameters via the props route.params object. So, for the book review example above, the screen component might look like this ...

import {useEffect} from 'react';
import { ScrollView, Text, View, StyleSheet } from 'react-native';

export default BookReviewScreen({ route }) => {
  const [book, setBook] = React.useState({});

  useEffect(() => {
    // fetch book using route.params.id
    // which was created in the navigation.navigate() 2nd param
  }, [route.params.id]);

  return (
    /* some JSX */
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Do not pass complex objects in params

The route.params are meant to pass simple key value pairs. Think of passing URL parameter. This allows your app to support sharing deep links for higher user engagement.

In React Native there are a handful of navigation events that you can listen for. There is an addListener method for the navigation object. The events you can listen for are focus, blur, state, and beforeRemove. focus and blur are like page load and unload events. state is when the state value of the navigator changes. beforeRemove occurs immediately before navigation happens

function SomeScreen({ navigation, route }) {
  useEffect(() => {
    const unsubscribeBR = navigation.addListener('beforeRemove', () => {
      // do something just before navigating
    });
    const unsubscribeF = navigation.addListener('focus', () => {
      //when this screen loads
    });
    //unsubscribeBR() or unsubscribeF() is the method you call to remove the listener
    return [unsubscribeBR, unsubscribeF];
  }, [navigation]);

  return (
    <View>
      <Text>SomeScreen</Text>
    </View>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# SafeAreas for React Navigation

When building a simple app we will usually use a combination of <SafeAreaProvider> and <SafeAreaView> on our page.

With React Navigation we will probably have a <NavigationContainer> at the top with <Stack.Navigator> and then the <Stack.Screen>. Inside the component loaded by the <Stack.Screen>, the recommended approach is to use the useSafeAreaInsets import so you can add the proper values to the style object in each screen.

import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Demo" component={Demo} />
        </Stack.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

function Demo() {
  const insets = useSafeAreaInsets();

  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'space-between',
        alignItems: 'center',

        // Paddings to handle safe area
        paddingTop: insets.top,
        paddingBottom: insets.bottom,
        paddingLeft: insets.left,
        paddingRight: insets.right,
      }}
    >
      <Text>This is top text.</Text>
      <Text>This is bottom text.</Text>
    </View>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

You could use <SafeAreaView> instead of <View> but there are some performance issues on some platforms. The best approach is to call the useSafeAreaInsets() hook to get the proper values.

# Lab Exercise

It is time to practice what you have learned. Build a simple two page Summary/Detail application. The first page will use the Expo FlatList Ref (opens new window) or RN FlatList Ref (opens new window) component to display a list of movie titles and the year it was released. Tapping on a movie list item should navigate to a new page displaying the detailed information for that one movie.

You will need the Pressable (opens new window) component to make the list items something that the user can interact with (pressable). You can wrap other elements with a Pressable component. Then it will handle events like onPress, onPressIn, onPressOut, and onLongPress.

Inside the Pressable component you will have a variable called pressed available to use as a boolean that indicates whether or not the pressable area is currently pressed. In the example below you can see pressed in the nested <Text> component as well as in the style attribute of the <Pressable> component.

<View>
  <Pressable
    onPress={(ev) => {
      someFunc(ev);
    }}
    style={({ pressed }) => [{ backgroundColor: pressed ? 'red' : 'blue' }]}
  >
    <Text>{pressed ? 'Pressed!' : 'Press Me'}</Text>
  </Pressable>
</View>
1
2
3
4
5
6
7
8
9
10

You will need to pass the movie episode_id as a route parameter.

You will need to create a context provider for the movie data, so that it is available to both the movie list screen and the movie detail screen.

Don't worry about fetching data from an API for this exercise. Simply import the array of movie data from this module.

movieData.js
export const movieData = [
  {
    title: 'A New Hope',
    episode_id: 4,
    opening_crawl:
      "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....",
    director: 'George Lucas',
    producer: 'Gary Kurtz, Rick McCallum',
    release_date: '1977-05-25',
    characters: [
      'http://swapi.dev/api/people/1/',
      'http://swapi.dev/api/people/2/',
      'http://swapi.dev/api/people/3/',
      'http://swapi.dev/api/people/4/',
      'http://swapi.dev/api/people/5/',
      'http://swapi.dev/api/people/6/',
      'http://swapi.dev/api/people/7/',
      'http://swapi.dev/api/people/8/',
      'http://swapi.dev/api/people/9/',
      'http://swapi.dev/api/people/10/',
      'http://swapi.dev/api/people/12/',
      'http://swapi.dev/api/people/13/',
      'http://swapi.dev/api/people/14/',
      'http://swapi.dev/api/people/15/',
      'http://swapi.dev/api/people/16/',
      'http://swapi.dev/api/people/18/',
      'http://swapi.dev/api/people/19/',
      'http://swapi.dev/api/people/81/'
    ],
    planets: [
      'http://swapi.dev/api/planets/1/',
      'http://swapi.dev/api/planets/2/',
      'http://swapi.dev/api/planets/3/'
    ],
    starships: [
      'http://swapi.dev/api/starships/2/',
      'http://swapi.dev/api/starships/3/',
      'http://swapi.dev/api/starships/5/',
      'http://swapi.dev/api/starships/9/',
      'http://swapi.dev/api/starships/10/',
      'http://swapi.dev/api/starships/11/',
      'http://swapi.dev/api/starships/12/',
      'http://swapi.dev/api/starships/13/'
    ],
    vehicles: [
      'http://swapi.dev/api/vehicles/4/',
      'http://swapi.dev/api/vehicles/6/',
      'http://swapi.dev/api/vehicles/7/',
      'http://swapi.dev/api/vehicles/8/'
    ],
    species: [
      'http://swapi.dev/api/species/1/',
      'http://swapi.dev/api/species/2/',
      'http://swapi.dev/api/species/3/',
      'http://swapi.dev/api/species/4/',
      'http://swapi.dev/api/species/5/'
    ],
    created: '2014-12-10T14:23:31.880000Z',
    edited: '2014-12-20T19:49:45.256000Z',
    url: 'http://swapi.dev/api/films/1/'
  },
  {
    title: 'The Empire Strikes Back',
    episode_id: 5,
    opening_crawl:
      'It is a dark time for the\r\nRebellion. Although the Death\r\nStar has been destroyed,\r\nImperial troops have driven the\r\nRebel forces from their hidden\r\nbase and pursued them across\r\nthe galaxy.\r\n\r\nEvading the dreaded Imperial\r\nStarfleet, a group of freedom\r\nfighters led by Luke Skywalker\r\nhas established a new secret\r\nbase on the remote ice world\r\nof Hoth.\r\n\r\nThe evil lord Darth Vader,\r\nobsessed with finding young\r\nSkywalker, has dispatched\r\nthousands of remote probes into\r\nthe far reaches of space....',
    director: 'Irvin Kershner',
    producer: 'Gary Kurtz, Rick McCallum',
    release_date: '1980-05-17',
    characters: [
      'http://swapi.dev/api/people/1/',
      'http://swapi.dev/api/people/2/',
      'http://swapi.dev/api/people/3/',
      'http://swapi.dev/api/people/4/',
      'http://swapi.dev/api/people/5/',
      'http://swapi.dev/api/people/10/',
      'http://swapi.dev/api/people/13/',
      'http://swapi.dev/api/people/14/',
      'http://swapi.dev/api/people/18/',
      'http://swapi.dev/api/people/20/',
      'http://swapi.dev/api/people/21/',
      'http://swapi.dev/api/people/22/',
      'http://swapi.dev/api/people/23/',
      'http://swapi.dev/api/people/24/',
      'http://swapi.dev/api/people/25/',
      'http://swapi.dev/api/people/26/'
    ],
    planets: [
      'http://swapi.dev/api/planets/4/',
      'http://swapi.dev/api/planets/5/',
      'http://swapi.dev/api/planets/6/',
      'http://swapi.dev/api/planets/27/'
    ],
    starships: [
      'http://swapi.dev/api/starships/3/',
      'http://swapi.dev/api/starships/10/',
      'http://swapi.dev/api/starships/11/',
      'http://swapi.dev/api/starships/12/',
      'http://swapi.dev/api/starships/15/',
      'http://swapi.dev/api/starships/17/',
      'http://swapi.dev/api/starships/21/',
      'http://swapi.dev/api/starships/22/',
      'http://swapi.dev/api/starships/23/'
    ],
    vehicles: [
      'http://swapi.dev/api/vehicles/8/',
      'http://swapi.dev/api/vehicles/14/',
      'http://swapi.dev/api/vehicles/16/',
      'http://swapi.dev/api/vehicles/18/',
      'http://swapi.dev/api/vehicles/19/',
      'http://swapi.dev/api/vehicles/20/'
    ],
    species: [
      'http://swapi.dev/api/species/1/',
      'http://swapi.dev/api/species/2/',
      'http://swapi.dev/api/species/3/',
      'http://swapi.dev/api/species/6/',
      'http://swapi.dev/api/species/7/'
    ],
    created: '2014-12-12T11:26:24.656000Z',
    edited: '2014-12-15T13:07:53.386000Z',
    url: 'http://swapi.dev/api/films/2/'
  },
  {
    title: 'Return of the Jedi',
    episode_id: 6,
    opening_crawl:
      'Luke Skywalker has returned to\r\nhis home planet of Tatooine in\r\nan attempt to rescue his\r\nfriend Han Solo from the\r\nclutches of the vile gangster\r\nJabba the Hutt.\r\n\r\nLittle does Luke know that the\r\nGALACTIC EMPIRE has secretly\r\nbegun construction on a new\r\narmored space station even\r\nmore powerful than the first\r\ndreaded Death Star.\r\n\r\nWhen completed, this ultimate\r\nweapon will spell certain doom\r\nfor the small band of rebels\r\nstruggling to restore freedom\r\nto the galaxy...',
    director: 'Richard Marquand',
    producer: 'Howard G. Kazanjian, George Lucas, Rick McCallum',
    release_date: '1983-05-25',
    characters: [
      'http://swapi.dev/api/people/1/',
      'http://swapi.dev/api/people/2/',
      'http://swapi.dev/api/people/3/',
      'http://swapi.dev/api/people/4/',
      'http://swapi.dev/api/people/5/',
      'http://swapi.dev/api/people/10/',
      'http://swapi.dev/api/people/13/',
      'http://swapi.dev/api/people/14/',
      'http://swapi.dev/api/people/16/',
      'http://swapi.dev/api/people/18/',
      'http://swapi.dev/api/people/20/',
      'http://swapi.dev/api/people/21/',
      'http://swapi.dev/api/people/22/',
      'http://swapi.dev/api/people/25/',
      'http://swapi.dev/api/people/27/',
      'http://swapi.dev/api/people/28/',
      'http://swapi.dev/api/people/29/',
      'http://swapi.dev/api/people/30/',
      'http://swapi.dev/api/people/31/',
      'http://swapi.dev/api/people/45/'
    ],
    planets: [
      'http://swapi.dev/api/planets/1/',
      'http://swapi.dev/api/planets/5/',
      'http://swapi.dev/api/planets/7/',
      'http://swapi.dev/api/planets/8/',
      'http://swapi.dev/api/planets/9/'
    ],
    starships: [
      'http://swapi.dev/api/starships/2/',
      'http://swapi.dev/api/starships/3/',
      'http://swapi.dev/api/starships/10/',
      'http://swapi.dev/api/starships/11/',
      'http://swapi.dev/api/starships/12/',
      'http://swapi.dev/api/starships/15/',
      'http://swapi.dev/api/starships/17/',
      'http://swapi.dev/api/starships/22/',
      'http://swapi.dev/api/starships/23/',
      'http://swapi.dev/api/starships/27/',
      'http://swapi.dev/api/starships/28/',
      'http://swapi.dev/api/starships/29/'
    ],
    vehicles: [
      'http://swapi.dev/api/vehicles/8/',
      'http://swapi.dev/api/vehicles/16/',
      'http://swapi.dev/api/vehicles/18/',
      'http://swapi.dev/api/vehicles/19/',
      'http://swapi.dev/api/vehicles/24/',
      'http://swapi.dev/api/vehicles/25/',
      'http://swapi.dev/api/vehicles/26/',
      'http://swapi.dev/api/vehicles/30/'
    ],
    species: [
      'http://swapi.dev/api/species/1/',
      'http://swapi.dev/api/species/2/',
      'http://swapi.dev/api/species/3/',
      'http://swapi.dev/api/species/5/',
      'http://swapi.dev/api/species/6/',
      'http://swapi.dev/api/species/8/',
      'http://swapi.dev/api/species/9/',
      'http://swapi.dev/api/species/10/',
      'http://swapi.dev/api/species/15/'
    ],
    created: '2014-12-18T10:39:33.255000Z',
    edited: '2014-12-20T09:48:37.462000Z',
    url: 'http://swapi.dev/api/films/3/'
  },
  {
    title: 'The Phantom Menace',
    episode_id: 1,
    opening_crawl:
      'Turmoil has engulfed the\r\nGalactic Republic. The taxation\r\nof trade routes to outlying star\r\nsystems is in dispute.\r\n\r\nHoping to resolve the matter\r\nwith a blockade of deadly\r\nbattleships, the greedy Trade\r\nFederation has stopped all\r\nshipping to the small planet\r\nof Naboo.\r\n\r\nWhile the Congress of the\r\nRepublic endlessly debates\r\nthis alarming chain of events,\r\nthe Supreme Chancellor has\r\nsecretly dispatched two Jedi\r\nKnights, the guardians of\r\npeace and justice in the\r\ngalaxy, to settle the conflict....',
    director: 'George Lucas',
    producer: 'Rick McCallum',
    release_date: '1999-05-19',
    characters: [
      'http://swapi.dev/api/people/2/',
      'http://swapi.dev/api/people/3/',
      'http://swapi.dev/api/people/10/',
      'http://swapi.dev/api/people/11/',
      'http://swapi.dev/api/people/16/',
      'http://swapi.dev/api/people/20/',
      'http://swapi.dev/api/people/21/',
      'http://swapi.dev/api/people/32/',
      'http://swapi.dev/api/people/33/',
      'http://swapi.dev/api/people/34/',
      'http://swapi.dev/api/people/35/',
      'http://swapi.dev/api/people/36/',
      'http://swapi.dev/api/people/37/',
      'http://swapi.dev/api/people/38/',
      'http://swapi.dev/api/people/39/',
      'http://swapi.dev/api/people/40/',
      'http://swapi.dev/api/people/41/',
      'http://swapi.dev/api/people/42/',
      'http://swapi.dev/api/people/43/',
      'http://swapi.dev/api/people/44/',
      'http://swapi.dev/api/people/46/',
      'http://swapi.dev/api/people/47/',
      'http://swapi.dev/api/people/48/',
      'http://swapi.dev/api/people/49/',
      'http://swapi.dev/api/people/50/',
      'http://swapi.dev/api/people/51/',
      'http://swapi.dev/api/people/52/',
      'http://swapi.dev/api/people/53/',
      'http://swapi.dev/api/people/54/',
      'http://swapi.dev/api/people/55/',
      'http://swapi.dev/api/people/56/',
      'http://swapi.dev/api/people/57/',
      'http://swapi.dev/api/people/58/',
      'http://swapi.dev/api/people/59/'
    ],
    planets: [
      'http://swapi.dev/api/planets/1/',
      'http://swapi.dev/api/planets/8/',
      'http://swapi.dev/api/planets/9/'
    ],
    starships: [
      'http://swapi.dev/api/starships/31/',
      'http://swapi.dev/api/starships/32/',
      'http://swapi.dev/api/starships/39/',
      'http://swapi.dev/api/starships/40/',
      'http://swapi.dev/api/starships/41/'
    ],
    vehicles: [
      'http://swapi.dev/api/vehicles/33/',
      'http://swapi.dev/api/vehicles/34/',
      'http://swapi.dev/api/vehicles/35/',
      'http://swapi.dev/api/vehicles/36/',
      'http://swapi.dev/api/vehicles/37/',
      'http://swapi.dev/api/vehicles/38/',
      'http://swapi.dev/api/vehicles/42/'
    ],
    species: [
      'http://swapi.dev/api/species/1/',
      'http://swapi.dev/api/species/2/',
      'http://swapi.dev/api/species/6/',
      'http://swapi.dev/api/species/11/',
      'http://swapi.dev/api/species/12/',
      'http://swapi.dev/api/species/13/',
      'http://swapi.dev/api/species/14/',
      'http://swapi.dev/api/species/15/',
      'http://swapi.dev/api/species/16/',
      'http://swapi.dev/api/species/17/',
      'http://swapi.dev/api/species/18/',
      'http://swapi.dev/api/species/19/',
      'http://swapi.dev/api/species/20/',
      'http://swapi.dev/api/species/21/',
      'http://swapi.dev/api/species/22/',
      'http://swapi.dev/api/species/23/',
      'http://swapi.dev/api/species/24/',
      'http://swapi.dev/api/species/25/',
      'http://swapi.dev/api/species/26/',
      'http://swapi.dev/api/species/27/'
    ],
    created: '2014-12-19T16:52:55.740000Z',
    edited: '2014-12-20T10:54:07.216000Z',
    url: 'http://swapi.dev/api/films/4/'
  },
  {
    title: 'Attack of the Clones',
    episode_id: 2,
    opening_crawl:
      'There is unrest in the Galactic\r\nSenate. Several thousand solar\r\nsystems have declared their\r\nintentions to leave the Republic.\r\n\r\nThis separatist movement,\r\nunder the leadership of the\r\nmysterious Count Dooku, has\r\nmade it difficult for the limited\r\nnumber of Jedi Knights to maintain \r\npeace and order in the galaxy.\r\n\r\nSenator Amidala, the former\r\nQueen of Naboo, is returning\r\nto the Galactic Senate to vote\r\non the critical issue of creating\r\nan ARMY OF THE REPUBLIC\r\nto assist the overwhelmed\r\nJedi....',
    director: 'George Lucas',
    producer: 'Rick McCallum',
    release_date: '2002-05-16',
    characters: [
      'http://swapi.dev/api/people/2/',
      'http://swapi.dev/api/people/3/',
      'http://swapi.dev/api/people/6/',
      'http://swapi.dev/api/people/7/',
      'http://swapi.dev/api/people/10/',
      'http://swapi.dev/api/people/11/',
      'http://swapi.dev/api/people/20/',
      'http://swapi.dev/api/people/21/',
      'http://swapi.dev/api/people/22/',
      'http://swapi.dev/api/people/33/',
      'http://swapi.dev/api/people/35/',
      'http://swapi.dev/api/people/36/',
      'http://swapi.dev/api/people/40/',
      'http://swapi.dev/api/people/43/',
      'http://swapi.dev/api/people/46/',
      'http://swapi.dev/api/people/51/',
      'http://swapi.dev/api/people/52/',
      'http://swapi.dev/api/people/53/',
      'http://swapi.dev/api/people/58/',
      'http://swapi.dev/api/people/59/',
      'http://swapi.dev/api/people/60/',
      'http://swapi.dev/api/people/61/',
      'http://swapi.dev/api/people/62/',
      'http://swapi.dev/api/people/63/',
      'http://swapi.dev/api/people/64/',
      'http://swapi.dev/api/people/65/',
      'http://swapi.dev/api/people/66/',
      'http://swapi.dev/api/people/67/',
      'http://swapi.dev/api/people/68/',
      'http://swapi.dev/api/people/69/',
      'http://swapi.dev/api/people/70/',
      'http://swapi.dev/api/people/71/',
      'http://swapi.dev/api/people/72/',
      'http://swapi.dev/api/people/73/',
      'http://swapi.dev/api/people/74/',
      'http://swapi.dev/api/people/75/',
      'http://swapi.dev/api/people/76/',
      'http://swapi.dev/api/people/77/',
      'http://swapi.dev/api/people/78/',
      'http://swapi.dev/api/people/82/'
    ],
    planets: [
      'http://swapi.dev/api/planets/1/',
      'http://swapi.dev/api/planets/8/',
      'http://swapi.dev/api/planets/9/',
      'http://swapi.dev/api/planets/10/',
      'http://swapi.dev/api/planets/11/'
    ],
    starships: [
      'http://swapi.dev/api/starships/21/',
      'http://swapi.dev/api/starships/32/',
      'http://swapi.dev/api/starships/39/',
      'http://swapi.dev/api/starships/43/',
      'http://swapi.dev/api/starships/47/',
      'http://swapi.dev/api/starships/48/',
      'http://swapi.dev/api/starships/49/',
      'http://swapi.dev/api/starships/52/',
      'http://swapi.dev/api/starships/58/'
    ],
    vehicles: [
      'http://swapi.dev/api/vehicles/4/',
      'http://swapi.dev/api/vehicles/44/',
      'http://swapi.dev/api/vehicles/45/',
      'http://swapi.dev/api/vehicles/46/',
      'http://swapi.dev/api/vehicles/50/',
      'http://swapi.dev/api/vehicles/51/',
      'http://swapi.dev/api/vehicles/53/',
      'http://swapi.dev/api/vehicles/54/',
      'http://swapi.dev/api/vehicles/55/',
      'http://swapi.dev/api/vehicles/56/',
      'http://swapi.dev/api/vehicles/57/'
    ],
    species: [
      'http://swapi.dev/api/species/1/',
      'http://swapi.dev/api/species/2/',
      'http://swapi.dev/api/species/6/',
      'http://swapi.dev/api/species/12/',
      'http://swapi.dev/api/species/13/',
      'http://swapi.dev/api/species/15/',
      'http://swapi.dev/api/species/28/',
      'http://swapi.dev/api/species/29/',
      'http://swapi.dev/api/species/30/',
      'http://swapi.dev/api/species/31/',
      'http://swapi.dev/api/species/32/',
      'http://swapi.dev/api/species/33/',
      'http://swapi.dev/api/species/34/',
      'http://swapi.dev/api/species/35/'
    ],
    created: '2014-12-20T10:57:57.886000Z',
    edited: '2014-12-20T20:18:48.516000Z',
    url: 'http://swapi.dev/api/films/5/'
  },
  {
    title: 'Revenge of the Sith',
    episode_id: 3,
    opening_crawl:
      'War! The Republic is crumbling\r\nunder attacks by the ruthless\r\nSith Lord, Count Dooku.\r\nThere are heroes on both sides.\r\nEvil is everywhere.\r\n\r\nIn a stunning move, the\r\nfiendish droid leader, General\r\nGrievous, has swept into the\r\nRepublic capital and kidnapped\r\nChancellor Palpatine, leader of\r\nthe Galactic Senate.\r\n\r\nAs the Separatist Droid Army\r\nattempts to flee the besieged\r\ncapital with their valuable\r\nhostage, two Jedi Knights lead a\r\ndesperate mission to rescue the\r\ncaptive Chancellor....',
    director: 'George Lucas',
    producer: 'Rick McCallum',
    release_date: '2005-05-19',
    characters: [
      'http://swapi.dev/api/people/1/',
      'http://swapi.dev/api/people/2/',
      'http://swapi.dev/api/people/3/',
      'http://swapi.dev/api/people/4/',
      'http://swapi.dev/api/people/5/',
      'http://swapi.dev/api/people/6/',
      'http://swapi.dev/api/people/7/',
      'http://swapi.dev/api/people/10/',
      'http://swapi.dev/api/people/11/',
      'http://swapi.dev/api/people/12/',
      'http://swapi.dev/api/people/13/',
      'http://swapi.dev/api/people/20/',
      'http://swapi.dev/api/people/21/',
      'http://swapi.dev/api/people/33/',
      'http://swapi.dev/api/people/35/',
      'http://swapi.dev/api/people/46/',
      'http://swapi.dev/api/people/51/',
      'http://swapi.dev/api/people/52/',
      'http://swapi.dev/api/people/53/',
      'http://swapi.dev/api/people/54/',
      'http://swapi.dev/api/people/55/',
      'http://swapi.dev/api/people/56/',
      'http://swapi.dev/api/people/58/',
      'http://swapi.dev/api/people/63/',
      'http://swapi.dev/api/people/64/',
      'http://swapi.dev/api/people/67/',
      'http://swapi.dev/api/people/68/',
      'http://swapi.dev/api/people/75/',
      'http://swapi.dev/api/people/78/',
      'http://swapi.dev/api/people/79/',
      'http://swapi.dev/api/people/80/',
      'http://swapi.dev/api/people/81/',
      'http://swapi.dev/api/people/82/',
      'http://swapi.dev/api/people/83/'
    ],
    planets: [
      'http://swapi.dev/api/planets/1/',
      'http://swapi.dev/api/planets/2/',
      'http://swapi.dev/api/planets/5/',
      'http://swapi.dev/api/planets/8/',
      'http://swapi.dev/api/planets/9/',
      'http://swapi.dev/api/planets/12/',
      'http://swapi.dev/api/planets/13/',
      'http://swapi.dev/api/planets/14/',
      'http://swapi.dev/api/planets/15/',
      'http://swapi.dev/api/planets/16/',
      'http://swapi.dev/api/planets/17/',
      'http://swapi.dev/api/planets/18/',
      'http://swapi.dev/api/planets/19/'
    ],
    starships: [
      'http://swapi.dev/api/starships/2/',
      'http://swapi.dev/api/starships/32/',
      'http://swapi.dev/api/starships/48/',
      'http://swapi.dev/api/starships/59/',
      'http://swapi.dev/api/starships/61/',
      'http://swapi.dev/api/starships/63/',
      'http://swapi.dev/api/starships/64/',
      'http://swapi.dev/api/starships/65/',
      'http://swapi.dev/api/starships/66/',
      'http://swapi.dev/api/starships/68/',
      'http://swapi.dev/api/starships/74/',
      'http://swapi.dev/api/starships/75/'
    ],
    vehicles: [
      'http://swapi.dev/api/vehicles/33/',
      'http://swapi.dev/api/vehicles/50/',
      'http://swapi.dev/api/vehicles/53/',
      'http://swapi.dev/api/vehicles/56/',
      'http://swapi.dev/api/vehicles/60/',
      'http://swapi.dev/api/vehicles/62/',
      'http://swapi.dev/api/vehicles/67/',
      'http://swapi.dev/api/vehicles/69/',
      'http://swapi.dev/api/vehicles/70/',
      'http://swapi.dev/api/vehicles/71/',
      'http://swapi.dev/api/vehicles/72/',
      'http://swapi.dev/api/vehicles/73/',
      'http://swapi.dev/api/vehicles/76/'
    ],
    species: [
      'http://swapi.dev/api/species/1/',
      'http://swapi.dev/api/species/2/',
      'http://swapi.dev/api/species/3/',
      'http://swapi.dev/api/species/6/',
      'http://swapi.dev/api/species/15/',
      'http://swapi.dev/api/species/19/',
      'http://swapi.dev/api/species/20/',
      'http://swapi.dev/api/species/23/',
      'http://swapi.dev/api/species/24/',
      'http://swapi.dev/api/species/25/',
      'http://swapi.dev/api/species/26/',
      'http://swapi.dev/api/species/27/',
      'http://swapi.dev/api/species/28/',
      'http://swapi.dev/api/species/29/',
      'http://swapi.dev/api/species/30/',
      'http://swapi.dev/api/species/33/',
      'http://swapi.dev/api/species/34/',
      'http://swapi.dev/api/species/35/',
      'http://swapi.dev/api/species/36/',
      'http://swapi.dev/api/species/37/'
    ],
    created: '2014-12-20T18:49:38.403000Z',
    edited: '2014-12-20T20:47:52.073000Z',
    url: 'http://swapi.dev/api/films/6/'
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506

Tip

Set-up the basic navigation first. Then add the movie data.

Last, add the SafeAreaContext to the App component and the SafeAreaView to the screen components.

Solution

prof3ssorSt3v3/mad9135-stack-navigation-demo (opens new window)

# Resources

React Navigation Fundamentals (opens new window)

Expo Vector Icons are discussed in more detail in Module 3.2

Last Updated: 9/18/2023, 9:34:16 AM