Skip to content

Exercise 04 : Movies

Purpose and learning process

In this exercise you will learn the basics of the React Native CLI development:

  • using and rendering build-in components
  • creating and rendering an own components
  • understand state and props
  • event handling
  • passing data between components
  • calling functions between components
  • using styles
  • navigating between screens
  • load and parse JSON data
  • touchable components

This tutorial teaches you to create React Native application with React Native CLI. App will load it’s data from the Movie DB. Application UI will be created with build-in React Native components and styles. Created project is tested with a emulator and a real device. To test with emulators you will need to install Android Studio or Xcode.

Example screenshots

Application will load data from The Movie DB and display movie based data with own components.

Movies 1 Movies 2

Example Video

Example video: https://youtu.be/tnKkw_Emv9s.

The Mobie DB

Sign up to The Movie DB and get the api key. Learn the basics of API and Getting Started.

You can find your API key from your profile/settings. An example request looks like this (replacing the api_key_here text with your own API key):

1
https://api.themoviedb.org/3/movie/76341?api_key=api_key_here

Note

Add &append_to_response=videos to your request, if you want to include trailers to response data

Example response will be returned with JSON data

Movies 3

Create a new project

Create a Movies App with React Native CLI. You can find setup instructions for Android and iOS from Setting up the development environment. Instructions are a bit different depending on your development operating system, follow instructions and set up your system ready for React Native CLI.

When your system is ready for React Native CLI development, type a following command to create a Movies project:

1
npx react-native init Movies

After that you can run your app for iOS or Android:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Run instructions for iOS:
    • cd Movies && npx react-native run-ios
    - or -
    • Open Movies/ios/Movies.xcworkspace in Xcode or run "xed -b ios"
    • Hit the Run button

Run instructions for Android:
    • Have an Android emulator running (quickest way to get started), 
      or a device connected.
    • cd Movies && npx react-native run-android

Run your project

For example running in Android, first start Android emulator and then give a following command:

1
npx react-native run-android

Tip

If you want to run in specific Android device, use below adb devices command to list all devices in your computer.

1
adb devices
1
2
List of devices attached
emulator-5554   device
And then use command to run in specific device
1
npx react-native run-android --deviceId=DEVICE_ID
For example in this case:
1
npx react-native run-android --deviceId=emulator-5554

Error: SDK location not found

You might get a following "SDK location not found" error message:

1
2
3
SDK location not found. Define location with an ANDROID_SDK_ROOT environment 
variable or by setting the sdk.dir path in your project's local properties file 
at '/Users/USERNAME/ReactNative/Movies/android/local.properties'.

SO, you need to and on ANDROID_SDK_ROOT environment variable or create a local.properties file to your android folder in your project. You can find more nice instructions for example here: React Native Android Build Failed

Go to the android directory of your react-native project and create a file called local.properties with this line in macOS:

1
sdk.dir = /Users/USERNAME/Library/Android/sdk

Where USERNAME is your macOS username or in Windows (check your sdk location, might be different):

1
sdk.dir = C:\\Users\\USERNAME\\AppData\\Local\\Android\\sdk

You might get also error message that adb is not found, then you need to modify your path to point also to platform-tools folder where adb is.

1
export PATH=$PATH:~/Library/Android/sdk/platform-tools/

All ok - hopefully

After you get CLI started successfully, you should see your new app running in the emulator shortly:

Movies 4 Movies 5

Using JavaScript Instead of TypeScript

React Native defaults new applications to TypeScript, but JavaScript may still be used. Files with a .jsx extension are treated as JavaScript instead of TypeScript, and will not be typechecked. JavaScript modules may still be imported by TypeScript modules, along with the reverse.

Rename your App.tsx file to App.jsx and modify your App content as below. Now you can continue using JavaScript (not TypeScript). OR you can modify exercise by yourself to support/use TypeScript, if you want to.

Modify App.jsx code to include only needed starting code for this project. Now app will render only SafeAreaView, StatusBar, View with one Text component.

 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
import React from 'react';
import {
  SafeAreaView,
  StatusBar,
  StyleSheet,
  Text,
  View
} from 'react-native';

function App() {

  return (
    <SafeAreaView>
      <StatusBar/>
      <View style={styles.sectionContainer}>
        <Text>Hello React Native CLI</Text>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  }
});

export default App;

Save your code or double tap R with your keyboard, if emulator isn't refresh automatically.

Movies 6

Load movies and show data

Read React Native Networking material to get familiar with the basic's of Networking. Fetch is good, when data is JSON based. Of course you can use some 3th party libraries like Axios or Axios npm.

Tip

You can find nice materials about React in left course menu!

Install and use Axios

Install Axios and use it later to load JSON data in your application.

1
npm install axios

Modify App.js code and import axios.

1
2
// other imports
import axios from 'axios';

MovieList component

Create a new MoviesList function component inside your App.js.

1
2
3
4
5
function MoviesList() {
  return (
    <Text>Test</Text>
  )
}

OR you can define your MovieList component with const:

1
2
3
4
5
const MoviesList = () => {
  return (
    <Text>Test</Text>
  )
}

And remember use it in main App.

1
2
3
4
5
6
7
<SafeAreaView>
  <StatusBar/>
  <ScrollView contentInsetAdjustmentBehavior="automatic">
    <MoviesList/>
  </ScrollView>
</SafeAreaView>
};

Use React hooks to store loaded movies data in your MovieList component.

1
const [movies, setMovies] = useState([]); 

Tip

Remember import useState and useEffect from react library. useEffect will be used later.

1
import React, { useState, useEffect } from 'react';

Load movies data with Axios and React effect hooks in your MovieList.

1
2
3
4
5
6
7
8
9
useEffect(() => {
  axios
    .get('https://api.themoviedb.org/3/movie/now_playing?api_key=YOUR_API_KEY_HERE&append_to_response=videos')
    .then(response => {
      // check console - a movie data should be visible there
      console.log(response.data.results);
      setMovies(response.data.results);
    })
}, [])

Note

Remember use your own API key in above command

Now movies data should be loaded and stored to movies state in MoviesList component.

MovieListItem component component

Create a new MovieListItem function component. This component will render one movie data in the UI. Now it will only display movie title text.

1
2
3
4
5
6
7
function MovieListItem(props) {
  return(
    <View style={styles.movieItem}>
    <Text style={styles.movieItemTitle}>{props.movie.title}</Text>
    </View>
  )
}

OR create it with const:

1
2
3
4
5
6
7
const MovieListItem = (props) => {
  return(
    <View style={styles.movieItem}>
    <Text style={styles.movieItemTitle}>{props.movie.title}</Text>
    </View>
  )
}

Use MovieListItem component in MoviesList component rendering. map function loops through all the movies. Movie object will be send to MovieListItem component via props.

Now conditional rendering is done with movies array length. Loading, please wait... is displayed, when data is loading (if there aren't any movies inside a movies array). You should implement timeout to display for example Can't load movies text if data is not available.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
function MoviesList() {
  // ...

  if (movies.length === 0) {
    return(
      <View style={{flex: 1, padding: 20}}>
        <Text>Loading, please wait...</Text>
      </View>
    )
  }
  let movieItems = movies.map(function(movie,index){
    return (
      <MovieListItem movie={movie} key={index}/>
    )
  });

  return (
    <ScrollView>
      {movieItems}
    </ScrollView>
  )
}

Modify your app css-styles.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const styles = StyleSheet.create({
  movieItem: {
    margin: 5,
    flex: 1,
    flexDirection: 'row'
  },
  movieItemImage: {
    marginRight: 5
  },
  movieItemTitle: {
    fontWeight: 'bold',
  },
  movieItemText: {
    flexWrap: 'wrap'
  }
});

Double tap R in your emulator. Movie's show titles should be visible now.

Movies 7

Modify MovieListItem to display more movie data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function MovieListItem(props) {
  let IMAGEPATH = 'http://image.tmdb.org/t/p/w500'
  let imageurl = IMAGEPATH + props.movie.poster_path;

  return (
    <View style={styles.movieItem}>
      <View style={styles.movieItemImage}>
        <Image source={{uri: imageurl}} style={{width: 99, height: 146}} />
      </View>
      <View style={{marginRight: 50}}>
        <Text style={styles.movieItemTitle}>{props.movie.title}</Text>
        <Text style={styles.movieItemText}>{props.movie.release_date}</Text>
        <Text numberOfLines={6} ellipsizeMode="tail" style={styles.movieItemText}>{props.movie.overview}</Text>
      </View> 
    </View>
  )
}

Tip

Remember import Image from react-native.

Test

And now, refresh your app again and try scrolling it's view. Image will be visible at the left and movie data in right.

Movies 8 Movies 9

Add a navigation and a movie details page

Mobile apps are rarely made up of a single screen. Managing the presentation of, and transition between, multiple screens is typically handled by what is known as a navigator.

Read more information React Navigation.

Install React Navigation to your project. Read above documentation and learn which npm packages you need to install. Remember you are now working with React Native CLI project.

1
2
npm install @react-navigation/native @react-navigation/native-stack
npm install react-native-screens react-native-safe-area-context

Create a new MovieListScreen.js file and copy all App.js content inside it. Modify App declaration to be a MovieListScreen component.

1
const MovieListScreen: () => Node = ({ navigation }) => {
1
export default MovieListScreen;

Note

Add { navigation } to pass navigation object to MovieListScreen.

App.js need to be changed to generate a app navigation. React Navigation uses a stack navigator to manage the navigation history and presentation of the appropriate screen based on the route taken by a user inside the app. Now App only have a MovieListScreen, so it will be used, when an application is launched.

Modify App.js to include navigation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// App.js
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import MovieListScreen from './MovieListScreen';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="MoviesList"
          component={MovieListScreen}
          options={{ title: 'MovieList' }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Test your application. It should work the same way as without a navigation, except the navigation bar is now visible. If you got some errors like "RNSScreenStackHeaderConfig" was not found in the UIManager, just uninstall your app from the emulator/phone and kill Metro Builder. Install your app again, it should work.

Movies 10

Create a new MovieDetailScreen.js file. This will be used to display information about selected movie from the movie list.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import React from 'react';
import {
  Text,
  View
} from 'react-native';

export default function MovieDetailScreen() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>MovieDetailScreen</Text>
      </View>
    )
}

Modify App.js to include navigation to this MovieDetailScreen.

 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
// App.js
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import MovieListScreen from './MovieListScreen';
import MovieDetailScreen from './MovieDetailScreen';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="MoviesList"
          component={MovieListScreen}
          options={{ title: 'MovieList' }}
        />
        <Stack.Screen 
        name="MovieDetails" 
        component={MovieDetailScreen} 
        options={{ title: 'MovieDetails' }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Modify MovieListItems creation in a MoviesList rendering. Add TouchableHighlight around MovieListItem to handle a tap gesture.

1
2
3
4
5
6
7
let movieItems = movies.map(function(movie,index){
  return (
    <TouchableHighlight onPress={_ => itemPressed(index)} 
                  underlayColor="lightgray" key={index}>
      <MovieListItem movie={movie} key={index}/>
    </TouchableHighlight>
  )

Note

Remember add import to TouchableHighlight and create a following itemPressed function:

Add itemPressed function.

1
2
3
const itemPressed = (index) => {
  alert(index);
}

Save and/or double tap R in your emulator and click one of the movie rows. You should see Alert and row number.

Movies 11

Add navigation to MovieDetail page. Remove alert(index) line and add navigation to MovieDetail screen:

1
2
3
4
5
6
function MoviesList(props) {
  //...
  const itemPressed = (index) => {
    props.navigation.navigate('MovieDetails');
  }
  //...

Now above code is in the child component (MoviesList) and you can't use navigation from there without sending it from the parent component. So, you need to modify MovieListScreen class to pass navigation as a props. Modify MovieListScreen render to pass a main navigation to MoviesList child component via props.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const MovieListScreen: () => Node = ({ navigation }) => {
  return (
    <SafeAreaView>
      <StatusBar/>
      <ScrollView contentInsetAdjustmentBehavior="automatic">
       <MoviesList navigation={ navigation }/>
      </ScrollView>
    </SafeAreaView>
  );
};

Now test your app and you should can move between main and detail screens. Of course MovieDetail screen is still blank.

Movies 12 Movies 13

Note

Read more about navigation here: React Navigation

Show movie details in MovieDetailScreen

You can pass a parameters to the new screen, when you are using a navigation. Modify itemPressed function to send selected movie data to the MovieDetail screen.

1
2
3
4
5
6
const itemPressed = (index) => {
  //alert(index);
  props.navigation.navigate(
    'MovieDetails',
    { movie: movies[index] });
}

Modify MovieDetailScreen to show movie details. route object will be passed here via props and it will be used to get movie data from navigation parameters. Component will render movie poster with Image component and a few data with Text component.

 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
import React from 'react';
import {
  Text,
  View,
  StyleSheet,
  Image
} from 'react-native';

export default function MovieDetailScreen(props) {
  const { route } = props;
  const { movie } = route.params; 
  let IMAGEPATH = 'http://image.tmdb.org/t/p/w500';
  let imageurl = IMAGEPATH + movie.backdrop_path;

  return (
    <View>
      <Image source={{uri: imageurl}} style={styles.image}  />
      <Text style={styles.title}>{movie.title}</Text>
      <Text style={styles.text}>{movie.release_date}</Text>
      <Text style={styles.text}>{movie.overview}</Text>
    </View>
  )
}
const styles = StyleSheet.create({
  image: {
    aspectRatio: 670/250
  },
  title: {
    fontWeight: 'bold',
    fontSize: 15
  },
  text: {
    fontSize: 12,
    flexWrap: 'wrap'
  }
});

Test

Save and test your app. Now it should work.

Movies 14 Movies 15

Push to GitLab

Test your application in emulator, take screenshots (add those to your project folder) and commit/push your mobile-exercises repository back to JAMKIT/GitLab. Remember move your exercise/issue ticket from Doing to In Review in Issues Board and write your learning comments to issue comments.