by

You need to build a mobile app that lists some data and shows details on a separate screen once user taps an item? Follow this complete guide on how to master React Native List and Navigator components to switch between a list and a detail screen.

If you don’t have any experience with React Native check out 3 Steps to Build Your First Mobile App with React Native. It explains the basics and how to set up required software to get started.

What are We Building

Our goal is to build an app that lists top 10 movies of 2016 in a beautiful list with background images and detail on each movie on a separate screen. The app has to work on both, iOS and Android devices. And here is what we want it to look like.

Set up the Stage

Let’s start off by creating a new app.

Create a New App Project

Open Terminal App and run these commands to initialize a new project and run it the emulator.

react-native init Top10MoviesOf2016;
cd Top10MoviesOf2016 && react-native run-ios;

After that’s done, you should see React Native boilerplate app running in an emulator.

Enable Hot Reloading

Once your app is up and running, press D and select Enable Hot Reloading. This will save you some time having to reload the app manually every time you make a change.

To see how that works just open index.ios.js file and change Welcome to React native to something else and save the file. The app should get updated immediately, without manually reloading it. Isn’t it great?

Let’s Get Started

Open index.ios.js file and delete all of its content. Do the same for index.android.js. Let’s build our app from scratch to get a better understanding of how things work.

If you run your app on an iOS device, then index.ios.js gets executed, or index.android.js if you run it on Android. And, since we need our app to work on both platforms, we are going to have our app code in a separate file called App.js and then just import it in both index.ios.js and index.android.js to avoid repeating the same code twice.

So, go ahead and insert the following code into both, index.ios.js and index.android.js files.

import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('Top10MoviesOf2016', () => App);

All this code does, it just imports App component, which we haven’t created yet, and registers it as our main app component.

Once you save the file, you’re going to see an error screen. That screen is pretty self-explanatory and just tells us that App.js file we’re trying to import doesn’t exist. So, let’s fix it.

App Component

Create a file called App.js. And start off by importing components that we’ll be using.

import React, { Component } from 'react';
import {
  Navigator,    // Allows to navigate between different screens
  StatusBar,    // Allows to hide the status bar
  Text
} from 'react-native';

Next, let’s define a route mapper function that will be responsible for handling navigation between a list and a movie detail screens.

const RouteMapper = (route, navigationOperations, onComponentRef) => {
  if (route.name === 'list') {
    return (
      // TODO: Add List component
      <Text>The list is going to be here</Text>
    );
  } else if (route.name === 'movie') {
    // TODO: Add Movie component for a movie detail screen
  }
};

There is only two routes in our app: list for a list and movie for a movie detail screen. We have some temporary text instead of an actual list, for now, just to make sure that our route mapper works as expected, and we’re going to replace it with an actual List component later. As you noticed, we put those handy to-do notes to remind us to update that code once we build List and Movie components.

And finally, let’s define App class. We’re going to use componentDidMount lifecycle hook, which runs after a component has been rendered, to hide the status bar. And we’ll use Navigator component, inside render() method, that will be handling navigation between screens.

export default class App extends Component {
  componentDidMount() {
    // Hide the status bar
    StatusBar.setHidden(true);
  }
  
  render() {
    return (
      // Handle navigation between screens
      <Navigator
        // Default to list route
        initialRoute={{name: 'list'}}
        // Use FloatFromBottom transition between screens
        configureScene={(route, routeStack) => Navigator.SceneConfigs.FloatFromBottom}
        // Pass a route mapper functions
        renderScene={RouteMapper}
      />
    );
  }
}

You can read more on available component lifecycle hooks here State and Lifecycle

Let’s check out how our app is looking so far. Go to the emulator, and press R to reload the app because that error broke hot reloading earlier and you have to reload it manually.

Ok, it looks like Navigator is working fine and it renders <Text>The list is going to be here</Text> component by default. That’s great.

List Component

It’s time to start building List component. Go ahead and create List.js file and start filling it up with the code.

Import Components

First, import components that we’re going to use.

import React, { Component } from 'react';
import {
  ListView,       // Renders a list
  RefreshControl, // Refreshes the list on pull down
  Text
} from 'react-native';

Get the Data for the List

The next step is to figure out how are we going to get the data for the list. There are a few ways to get that data.

  • Fetch from an API. That’s what you most likely would do in a real world app. You would have some backend for storing and serving the data for your mobile app.
  • Load it from a file. You can just store all of your data in a separate file and load it as needed. That would work if you had static data that doesn’t change over time.
  • Hardcode into the code. That’s the easiest way that would work for our example app. We’re going to be lazy and hardcode some sample date into the code.

To make it easier for our demo app purposes let’s just add an array of data to List.js file after import statements.

const demoData = [
  {
    title: 'Zootopia',
    rating: 98,
    image: 'https://resizing.flixster.com/KlpTsb1_ZvS6qRLZHs9ao5LC8rk=/740x290/v1.bjsxMDQ5NDU2O2o7MTcxNTc7MTIwMDsyMDQ4OzEzMjU',
    large: 'https://resizing.flixster.com/IBgqlHT_rKpHSVNsM0sXqbV4LRQ=/fit-in/1152x864/v1.bjsxMDQ5NDU2O2o7MTcxNTc7MTIwMDsyMDQ4OzEzMjU',
    plot: "The modern mammal metropolis of Zootopia is a city like no other. Comprised of habitat neighborhoods like ritzy Sahara Square and frigid Tundratown, it's a melting pot where animals from every environment live together-a place where no matter what you are, from the biggest elephant to the smallest shrew, you can be anything. But when rookie Officer Judy Hopps (voice of Ginnifer Goodwin) arrives, she discovers that being the first bunny on a police force of big, tough animals isn't so easy. Determined to prove herself, she jumps at the opportunity to crack a case, even if it means partnering with a fast-talking, scam-artist...",
  },
  {
    title: 'Hell or High Water',
    rating: 98,
    image: 'https://resizing.flixster.com/LLIZ382Kh4RT6CT9gqiUIEo0mBg=/740x290/v1.bjsxMTE5MTAzO2o7MTcxNTc7MTIwMDszMDAwOzIwMDA',
    large: 'https://resizing.flixster.com/DkiyuHT_3ElnSROaH9SmrU6PaSg=/fit-in/1152x864/v1.bjsxMTE5MTAzO2o7MTcxNTc7MTIwMDszMDAwOzIwMDA',
    plot: 'Texas brothers--Toby (Chris Pine), and Tanner (Ben Foster), come together after years divided to rob branches of the bank threatening to foreclose on their family land. For them, the hold-ups are just part of a last-ditch scheme to take back a future that seemed to have been stolen from under them. Justice seems to be theirs, until they find themselves on the radar of Texas Ranger, Marcus (Jeff Bridges) looking for one last grand pursuit on the eve of his retirement, and his half-Comanche partner, Alberto (Gil Birmingham). As the brothers plot a final bank heist to complete their scheme, and with the Rangers on their heels, a...',
  },
  {
    title: 'The Jungle Book',
    rating: 95,
    image: 'https://resizing.flixster.com/4n38K0ACGyX61Pwe7prYKrN4Eu0=/740x290/v1.bjsxMDUzNDE0O2o7MTcxNTc7MTIwMDsyMDk4OzExNzQ',
    large: 'https://resizing.flixster.com/Oye7lbY02WO0WV_KugmP5FlYHEA=/fit-in/1152x864/v1.bjs5NDIxMDM7ajsxNzE1NTsxMjAwOzUwMDA7MjgxMw',
    plot: 'In this reimagining of the classic collection of stories by Rudyard Kipling, director Jon Favreau uses visually stunning CGI to create the community of animals surrounding Mowgli (Neel Sethi), a human boy adopted by a pack of wolves. The appearance of a villainous tiger named Shere Khan (voiced byIdris Elba) forces Mowgli\'s guardian, the panther Bagheera (Ben Kingsley), to shepherd the child to safety in the "man village." Along the way, the boy meets an affable, lazy bear named Baloo (Bill Murray), as well as a snake with hypnotic powers (Scarlett Johansson) and an orangutan (Christopher Walken) who wants to harness...',
  },
  {
    title: 'Love & Friendship',
    rating: 98,
    image: 'https://resizing.flixster.com/eW-nwFBWfzeD-IY08qy6gI-prgc=/740x290/v1.bjsxMDk4NjAyO2o7MTcxNTc7MTIwMDs2MTQ0OzQwOTY',
    large: 'https://resizing.flixster.com/M25ijUAdQfuQpWm7BJr_ggz2X6M=/fit-in/1152x864/v1.bjsxMDk4NjAyO2o7MTcxNTc7MTIwMDs2MTQ0OzQwOTY',
    plot: 'Beautiful young widow Lady Susan Vernon visits to the estate of her in-laws to wait out the colourful rumours about her dalliances circulating through polite society. Whilst ensconced there, she decides to secure a husband for herself and a future for her eligible but reluctant daughter, Frederica. In doing so she attracts the simultaneous attentions of the young, handsome Reginald DeCourcy, the rich and silly Sir James Martin and the divinely handsome, but married, Lord Manwaring, complicating matters severely...',
  },
  {
    title: 'Finding Dory',
    rating: 94,
    image: 'https://resizing.flixster.com/S8_NJxnxvIA9tKpzeUnQudSDlTw=/740x290/v1.bjsxMTI5MDgyO2o7MTcxNTg7MTIwMDsxODAwOzg0MA',
    large: 'https://resizing.flixster.com/nmL3PCIDGK7eOxL-fcAZ8ApNb34=/fit-in/1152x864/v1.bjsxMDU5OTI3O2o7MTcxNTc7MTIwMDs0MjcyOzIzMDI',
    plot: '"Finding Dory" reunites the friendly-but-forgetful blue tang fish with her loved ones, and everyone learns a few things about the true meaning of family along the way. The all-new big-screen adventure dives into theaters in 2016, taking moviegoers back to the extraordinary underwater world from the original film...',
  },
  {
    title: 'Hunt for the Wilderpeople',
    rating: 98,
    image: 'https://resizing.flixster.com/Z2wwu1dCuCecg4TzOeJe82c_kjU=/740x290/v1.bjsxMDkyNjEyO2o7MTcxNTc7MTIwMDsyMDQ4OzEzNjM',
    large: 'https://resizing.flixster.com/yV1MZxQiMaVx8I3qfYbO0rDJLNg=/fit-in/1152x864/v1.bjsxMDkyNjEyO2o7MTcxNTc7MTIwMDsyMDQ4OzEzNjM',
    plot: 'Raised on hip-hop and foster care, defiant city kid Ricky gets a fresh start in the New Zealand countryside. He quickly finds himself at home with his new foster family: the loving Aunt Bella, the cantankerous Uncle Hec, and dog Tupac. When a tragedy strikes that threatens to ship Ricky to another home, both he and Hec go on the run in the bush. As a national manhunt ensues, the newly branded outlaws must face their options: go out in a blaze of glory or overcome their differences and survive as a family. Equal parts road comedy and rousing adventure story, director Taika Waititi (WHAT WE DO IN THE SHADOWS, upcoming...',
  },
  {
    title: 'Kubo and the Two Strings',
    rating: 97,
    image: 'https://resizing.flixster.com/6mem3h1225eHWjxuIp7DbwsQl5I=/740x290/v1.bjsxMTUxNzQ5O2o7MTcxNTg7MTIwMDsyMDQ4Ozg1OA',
    large: 'https://resizing.flixster.com/aA0m4xiyBEDpyCSZ5U-e_apqueI=/fit-in/1152x864/v1.bjsxMDE1NjA4O2o7MTcxNTY7MTIwMDsxMDI0OzEwMjQ',
    plot: 'Kubo and the Two Strings is an epic action-adventure set in a fantastical Japan from acclaimed animation studio LAIKA. Clever, kindhearted Kubo (voiced by Art Parkinson of "Game of Thrones") ekes out a humble living, telling stories to the people of his seaside town including Hosato (George Takei), Akihiro (Cary-Hiroyuki Tagawa), and Kameyo (Academy Award nominee Brenda Vaccaro). But his relatively quiet existence is shattered when he accidentally summons a spirit from his past which storms down from the heavens to enforce an age-old vendetta. Now on the run, Kubo joins forces with Monkey (Academy Award...',
  },
  {
    title: 'Captain America: Civil War',
    rating: 90,
    image: 'https://resizing.flixster.com/b4pCL3JsCWG7cHVQHSuKQICkGY4=/740x290/v1.bjsxMDYyMDcwO2o7MTcxNTc7MTIwMDsyMDAwOzEzMzM',
    large: 'https://resizing.flixster.com/Gqa03F1e9dz4oQR0NjrxUOxbYMc=/fit-in/1152x864/v1.aDsxMzQ5MzE7ajsxNzE0NjsxMjAwOzIxNTg7MTEzNg',
    plot: 'Marvel\'s "Captain America: Civil War" finds Steve Rogers leading the newly formed team of Avengers in their continued efforts to safeguard humanity. But after another incident involving the Avengers results in collateral damage, political pressure mounts to install a system of accountability, headed by a governing body to oversee and direct the team. The new status quo fractures the Avengers, resulting in two camps-one led by Steve Rogers and his desire for the Avengers to remain free to defend humanity without government interference, and the other following Tony Stark\'s surprising decision to support government...',
  },
  {
    title: 'Sing Street',
    rating: 97,
    image: 'https://resizing.flixster.com/D8Vx220eg3b8Rs_mNnB1UXQtB7Y=/740x290/v1.bjsxMDc1MjQ4O2o7MTcxNTc7MTIwMDsxMTcxOzc2OA',
    large: 'https://resizing.flixster.com/KgXoAetuj_hehmjej-tzme82SEg=/fit-in/1152x864/v1.bjsxMDc1MjQ4O2o7MTcxNTc7MTIwMDsxMTcxOzc2OA',
    plot: 'From director John Carney (ONCE, BEGIN AGAIN), SING STREET takes us back to 1980s Dublin seen through the eyes of a 14-year-old boy named Conor (Ferdia Walsh-Peelo) who is looking for a break from a home strained by his parents\' relationship and money troubles, while trying to adjust to his new inner-city public school where the kids are rough and the teachers are rougher. He finds a glimmer of hope in the mysterious, über-cool and beautiful Raphina (Lucy Boynton), and with the aim of winning her heart he invites her to star in his band\'s music videos. There\'s only one problem: he\'s not part of a band...yet. She...',
  },
  {
    title: 'Moonlight',
    rating: 99,
    image: 'https://resizing.flixster.com/uqgUagErgMx2ew7AUMX8pfU8ehQ=/740x290/v1.bjsxMjI0NTk3O2o7MTcxNTk7MTIwMDsxMjgwOzcyMA',
    large: 'https://resizing.flixster.com/uFehzMj2094RvPYwVaza7Ev6ec4=/fit-in/1152x864/v1.bjsxMjM2MDY2O2o7MTcxNTk7MTIwMDsyMDcyOzIwNzI',
    plot: 'The tender, heartbreaking story of a young man\'s struggle to find himself, told across three defining chapters in his life as he experiences the ecstasy, pain, and beauty of falling in love, while grappling with his own sexuality.',
  },
];

It’s an array of movie objects, each of which has a title, rating, image, large image, and plot.

Outline List Component Class

Let’s define List component class and outline what methods we’re going to need first, and go through each of them after.

export default class List extends Component {

  /**
   * Store the data for ListView
   */
  state = {}

  /**
   * Call _fetchData after component has been mounted
   */
  componentDidMount() {}

  /**
   * Prepare demo data for ListView component
   */
  _fetchData = () => {}

  /**
   * Render a row
   */
  _renderRow = (movie) => {}

  /**
   * Renders the list
   */
  render() {
    return <Text>List Component</Text>;
  }
}

You can export multiple classes, variables or functions, but only one can be exported as a default one.

Update App.js to use List Component

Let’s go on a little detour and open up App.js file to make it use List component that we just created.

Add import statement first.

import List from './List';

Then, find our to-do note and <Text>The list is going to be here</Text>

// TODO: Add List component
<Text>The list is going to be here</Text>

And replace it with <List> component

<List navigator={navigationOperations} />

Let’s bring up the emulator and make sure that <List> component is being rendered.

Continue Working on List.js

Now, let’s go back to List.js and continue working on it.

Define State

Let’s start with state. We’re going to use state to store movie data for ListView component. Let’s go ahead and add some stuff to our state = {} definition, so it’d look like following.

  state = {
    // ListView DataSource object
    dataSource: new ListView.DataSource({
      rowHasChanged: (row1, row2) => row1 !== row2,
    }),
    // Used for RefreshControl
    isRefreshing: false,
  }

new ListView.DataSource creates an instance of DataSource object, which we’re going to use to fill it up with our movie data and use it for ListView component to render it on the screen.

Prepare Data for ListView

Next, let’s update _fetchData_ and componentDidMount to load the actual data into this.state.dataSource once component has been mounted.

  componentDidMount() {
    // Fetch Data
    this._fetchData();
  }
  _fetchData = () => {
    // Data is being refreshed
    this.setState({ isRefreshing: true });
    this.setState({
      // Fill up DataSource with demo data
      dataSource: this.state.dataSource.cloneWithRows(demoData),
      // Data has been refreshed by now
      isRefreshing: false,
    });
  }

Render Row

Let’s update _renderRow, which will be rendering each row in the list.

  _renderRow = (movie) => {
    return (
      // TODO: Update with Row component
      <Text>{movie.title}</Text>
    );
  }

Render the List

Let’s update render() method.

  render() {
    return (
      <ListView
        // Data source from state
        dataSource={this.state.dataSource}
        // Row renderer method
        renderRow={this._renderRow}
        // Refresh the list on pull down
        refreshControl={
          <RefreshControl
            refreshing={this.state.isRefreshing}
            onRefresh={this._fetchData}
          />
        }
      />
    );
  }

It returns ListView component with dataSource, renderRow, and refreshControl props. The latter is not required, so if you’re not planning on refreshing your data, you can omit it.

Let’s check out how our app is looking so far.

Looks great. It just needs some styling work.

Row Component

It’s time to make our list beautiful. And to do so we’re going to create a reusable component called Row, and then update _renderRow() method in List.js to use it instead of simple Text that we have now.

Let’s start off by creating a new file called Row.js.

Import Components

Import components we’re going to use first.

import React, { Component } from 'react';
import {
  Image,              // Renders background image
  StyleSheet,         // CSS-like styles
  Text,               // Renders text
  TouchableOpacity,   // Handles row presses
  View                // Container component
} from 'react-native';
import Dimensions from 'Dimensions';

// Detect screen size to calculate row height
const screen = Dimensions.get('window');

Define Class

Define Row class.

export default class Row extends Component {

  // Extract movie and onPress props passed from List component
  render({ movie, onPress } = this.props) {
    // Extract values from movie object
    const { title, rating, image } = movie;
    return (
      // Row press handler
      <TouchableOpacity
        // Pass row style
        style={styles.row}
        // Call onPress function passed from List component when pressed
        onPress={onPress}
        // Dim row a little bit when pressed
        activeOpacity={0.7}
      >
        {/* Background image */}
        <Image source={{uri: image}} style={styles.imageBackground}>
          {/* Title */}
          <Text style={[styles.text, styles.title]}>{title.toUpperCase()}</Text>
          {/* Rating */}
          <View style={styles.rating}>
            {/* Icon */}
            <Image
              source={{uri: 'https://staticv2.rottentomatoes.com/static/images/icons/cf-lg.png'}}
              style={styles.icon}
            />
            {/* Value */}
            <Text style={[styles.text, styles.value]}>{rating}%</Text>
          </View>
        </Image>
      </TouchableOpacity>
    );
  }

}

It has only one, render() method, that returns Image component with some text and icon inside it, wrapped into TouchableOpacity component to allow user press on rows and navigate them to a separate movie detail screen using a function that we defined in _renderRow of List component in List.js file.

Styles

And lastly, define styles.

const styles = StyleSheet.create({
  // Row
  row: {
    paddingBottom: 4,                   // Add padding at the bottom
  },
  // Background image
  imageBackground: {
    height: screen.height / 3,          // Divide screen height by 3
    justifyContent: 'center',           // Center vertically
    alignItems: 'center',               // Center horizontally
  },
  // Shared text style
  text: {
    color: '#fff',                      // White text color
    backgroundColor: 'transparent',     // No background
    fontFamily: 'Avenir',               // Change default font
    fontWeight: 'bold',                 // Bold font
    // Add text shadow
    textShadowColor: '#222',
    textShadowOffset: { width: 1, height: 1 },
    textShadowRadius: 4,
  },
  // Movie title
  title: {
    fontSize: 22,                       // Bigger font size
  },
  // Rating row
  rating: {
    flexDirection: 'row',               // Arrange icon and rating in one line
  },
  // Certified fresh icon
  icon: {
    width: 22,                          // Set width
    height: 22,                         // Set height
    marginRight: 5,                     // Add some margin between icon and rating
  },
  // Rating value
  value: {
    fontSize: 16,                       // Smaller font size
  },
});

Update List.js to use Row Component

Let’s open up List.js file to make it use Row component that we just created.

Add import statement first.

import Row from './Row';

Then, find _renderRow() method with Text component and a to-do note.

  _renderRow = (movie) => {
    return (
      // TODO: Update with Row component
      <Text>{movie.title}</Text>
    );
  }

Remove to-do note, and replace Text component with <Row> component.

  _renderRow = (movie) => {
    return (
      <Row
        // Pass movie object
        movie={movie}
        // Pass a function to handle row presses
        onPress={()=>{
          // Navigate to a separate movie detail screen
          this.props.navigator.push({
            name: 'movie',
            movie: movie,
          });
        }}
      />
    );
  }

It returns Row component and passes movie object as movie prop, and a function to navigate users to a separate movie detail screen once a row is pressed as onPress prop.

Let’s bring up the emulator and see how our Row component is looking.

It looks much better. Great job so far. Hang on for a little while longer, and we’ll be done with the app soon.

Movie Screen

The last thing left to do is a movie detail screen that appears when a user taps on a movie in the list.

Let’s start off by creating a new file called Movie.js.

Import Components

Import components we’re going to use first.

import React, { Component } from 'react';
import {
  Image,              // Renders background image
  ScrollView,         // Scrollable container
  StyleSheet,         // CSS-like styles
  Text,               // Renders text
  TouchableOpacity,   // Handles button presses
  View                // Container component
} from 'react-native';

Define Class

Define Movie class.

export default class Movie extends Component {

  // Extract movie object passed as a prop from Row component
  render({ movie } = this.props) {
    // Extract values from movie object
    const { title, rating, large, plot } = movie;
    return (
      <View style={styles.container}>
        {/* Background image with large image */}
        <Image source={{uri: large}} style={styles.imageBackground}>
          {/* Use ScrollView in case plot is too large to fit on the screen */}
          <ScrollView
            style={{flex: 1}}
          >
            {/* Title */}
            <Text style={[styles.text, styles.title]}>{title.toUpperCase()}</Text>
            {/* Rating */}
            <View style={styles.rating}>
              {/* Icon */}
              <Image
                source={{uri: 'https://staticv2.rottentomatoes.com/static/images/icons/cf-lg.png'}}
                style={styles.icon}
              />
              {/* Value */}
              <Text style={[styles.text, styles.value]}>{rating}%</Text>
            </View>
            {/* Plot */}
            <View style={styles.plot}>
              <Text style={styles.plotText}>{plot}</Text>
            </View>
          </ScrollView>
          {/* Button container */}
          <View style={styles.buttonContainer}>
            {/* Press handler */}
            <TouchableOpacity
              // Go to the previous screen
              onPress={() => {this.props.navigator.pop();}}
              // Dim button a little bit when pressed
              activeOpacity={0.7}
              // Pass button style
              style={styles.button}
            >
              <Text style={styles.buttonText}>CLOSE</Text>
            </TouchableOpacity>
          </View>
        </Image>
      </View>
    );
  }
}

It has only one, render() method, that returns flexible View component, which takes up all of the screen space, and contains a background image, movie title, rating, plot, and close button to go back to the list, using navigator prop that we passed from RouteMapper in App.js file.

Styles

And lastly, define styles.

const styles = StyleSheet.create({
  // Main container
  container: {
    flex: 1,                            // Take up all screen space
    backgroundColor: '#333',            // Dark background
  },
  // Background image
  imageBackground: {
    flex: 1,                            // Take up all screen space
    padding: 20                         // Add padding for content inside
  },
  text: {
    backgroundColor: 'transparent',     // No background
    color: '#fff',                      // White text color
    fontFamily: 'Avenir',               // Change default font
    fontWeight: 'bold',                 // Bold font
    // Add text shadow
    textShadowColor: '#222',
    textShadowOffset: {width: 1, height: 1},
    textShadowRadius: 4,
  },
  title: {
    fontSize: 22,                       // Bigger font size
    marginTop: 30,                      // Add space between top screen edge
    marginBottom: 5,                    // Add space at the bottom
    textAlign: 'center',                // Center horizontally
  },
  rating: {
    flexDirection: 'row',               // Arrange icon and rating in one line
    justifyContent: 'center',           // Center horizontally
  },
  icon: {
    width: 22,                          // Set width
    height: 22,                         // Set height
    marginRight: 5,                     // Add some margin between icon and rating
  },
  value: {
    fontSize: 16,                       // Smaller font size
  },
  plot: {
    backgroundColor: 'rgba(255,255,255,0.5)', // Semi-transparent white background
    borderRadius: 10,                   // Rounder corners
    marginTop: 40,                      // Margin at the top
    padding: 10,                        // Padding for content inside
  },
  plotText: {
    color: '#333',                      // Dark text color
    fontFamily: 'Avenir',               // Change default font
    fontSize: 15,                       // Small font size
  },
  buttonContainer: {
    marginTop: 20,                      // Add some margin at the top
  },
  button: {
    backgroundColor: '#617D8A',         // Color the button
    padding: 15                         // Padding inside
  },
  buttonText: {
    color: '#fff',                      // White button text
    fontFamily: 'Avenir',               // Change default font
    fontWeight: 'bold',                 // Bold font
    textAlign: 'center',                // Center horizontally
  }
});

Update App.js to use Movie Component

Let’s open up App.js file to make it use Movie component that we just created.

Add import statement first.

import Movie from './Movie';

Then, find our to-do note

  } else if (route.name === 'movie') {
    // TODO: Add Movie component for a movie detail screen
  }

And replace it with <Movie> component

  } else if (route.name === 'movie') {
    return (
      <Movie
        // Pass movie object passed with route down as a prop
        movie={route.movie}
        // Pass navigationOperations as navigator prop
        navigator={navigationOperations}
      />
    );
  }

Now, let’s bring up the emulator and click on any movie to see how Movie component is working.

Looks pretty good. Congratulations! You did a great job!

The Best Part

And the best part of it is that you don’t have to do anything at all to adapt your code to run on Android. Just launch Android emulator and run react-native run-android in the terminal.

react-native run-android

Source Code

You can get the source code of the app using git. Just run in terminal:

To download the code execute in terminal:

git clone https://github.com/rationalappdev/top-movies-of-2016.git && cd top-movies-of-2016;

To install React Native along with all required modules and launch the app execute:

npm install;
react-native run-ios;

What’s Next

You’ve learned a lot on how to build beautiful lists for your apps. You can dive deeper into React Native documentation to find out more features available for ListView component. And if you have any questions, just leave a comment.

I hope you enjoyed the tutorial. Subscribe to find out about new tutorials and learn how to build amazing apps!

Recommended Reading

Spread the Word
  • Shoji Itagaki

    Hello,

    I’m developing the both iOS and android APL using React Native.

    React Native is great, and I would like to develop with React Native.

    A few days ago, I had one strange error.

    Basically I use react-native-hackathon-starter for TAB control.

    In one TAB environment, I added list-detail function (Master-Detail something like).

    React Native List App Complete How-To Guide is a good example and I made this example into TAB.

    The list screen and detail screen on one TAB work well on iOS but not on android.

    I could not believe this phenomenon because React Native is for iOS and android..

    I spend a few days to debug this.

    Fortunately I found the following info.
    ReactNative-0.38,a warning,’useNativeDriver’ is not supported #276
    https://github.com/exponentjs/ex-navigation/issues/276

    This article imply ReactNative-0.37 OK.
    I tried go back to ReactNative from 38 to36 and it was OK BOTH platform.

    I can not believe this workaround too, but it work well.

    If there is someone who is struggling on this kind problem, this info will be some help.

    Thanks

    Shoji

  • Pingback: Listview work on ios but does not work on android | FYTRO SPORTS()

  • Doug Sheridan

    Great tutorial as usual. All your image URL’s from List.js no longer exist though. I highly recommend updating them to keep this tutorial alive and strong! Thanks for the great post 🙂

  • Hi, in my case, the code in App doesn’t update in emulator. Maybe need to build again?

  • luisCarlos

    great tutorial, unfortunately, the navigation is now deprecated and the time to debug seems to be astronomical so I built the app in a single screen… with lots of problems, the read is good, the technical process, not so hot. for a newbe

    thank you for the read…. will this be upgraded to the new navigational process/functions.

    • ceejay orji

      in App.js, change everything in navigator to

      then in List.js, change the navigator.push to
      this.props.navigator.push({
      component: Movie,
      movie: movie,
      navigationBarHidden: true,
      });

      in movie class, change const { title, rating, large, plot } = movie to
      const { title, rating, large, plot } = this.props.route.movie;