React Native Cookbook
上QQ阅读APP看书,第一时间看更新

How to do it...

  1. Let's open App.js and import the dependencies we'll need in this app, as well as our data.json file we created in the previous Getting ready section. We'll also import a Device utility from ./utils/Device, which we will build in a later step:
import React, { Component } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import Device from './utils/Device';

import data from './data.json';
  1. Here, we're going to create the main App component and its basic layout. This top-level component will decide whether to render the phone or tablet UI. We are only rendering two Text elements. The renderDetail text should be displayed on tablets only and the renderMaster text should be displayed on phones and tablets:
export default class App extends Component {
  renderMaster() {
    return (
      <Text>Render on phone and tablets!!</Text>
    );
  }

  renderDetail() {
    if (Device.isTablet()) {
      return (
        <Text>Render on tablets only!!</Text>
      );
    }
  }

  render() {
    return (
      <View style={styles.content}>
        {this.renderMaster()}
        {this.renderDetail()}
      </View>
    );
  }
}
  1. Under the App component, we'll add a few basic styles. The styles temporarily include paddingTop: 40 so that our rendered text is not overlapped by the device's system bar:
const styles = StyleSheet.create({
  content: {
paddingTop: 40, flex: 1, flexDirection: 'row', }, });
  1. If we try to run our app as it is, it will fail with an error telling us that the Device module cannot be found, so let's create it. The purpose of this utility class is to calculate whether the current device is a phone or tablet, based on the screen dimensions. It will have an isTablet method and an isPhone method. We need to create a utils folder in the root of the project and add a Device.js for the utility. Now we can add the basic structure of the utility:
import { Dimensions, Alert } from 'react-native';

// Tablet portrait dimensions
const tablet = {
  width: 552,
  height: 960,
};

class Device {
  // Added in next steps
}

const device = new Device();
export default device;
  1. Let's start building out the utility by creating two methods: one to get the dimensions in portrait and the other to get the dimensions in landscape. Depending on the device rotation, the values of width and height will change, which is why we need these two methods to always get the correct values, whether the device is landscape or portrait:
class Device {
  getPortraitDimensions() {
    const { width, height } = Dimensions.get("window");

    return {
      width: Math.min(width, height),
      height: Math.max(width, height),
    };
  }

  getLandscapeDimensions() {
    const { width, height } = Dimensions.get("window");

    return {
      width: Math.max(width, height),
      height: Math.min(width, height),
    };
  }
}
  1. Now let's create the two methods our app will use to determine whether the app is running on a tablet or a phone. To calculate this, we need to get the dimensions in portrait mode and compare them with the dimensions we have defined for a tablet:
  isPhone() {
    const dimension = this.getPortraitDimensions();
    return dimension.height < tablet.height;
  }

  isTablet() {
    const dimension = this.getPortraitDimensions();
    return dimension.height >= tablet.height;
  }
  1. Now, if we open the app, we should see two different texts being rendered, depending on whether we're running the app on a phone or a tablet:
  1. The utility works as expected! Let's return to working on the renderMaster method of the main App.js. We want this method to render the list of contacts that live in the data.json file. Let's import a new component, which we'll build out in the following steps, and update the renderMaster method to use our new component:
import UserList from './UserList';

export default class App extends Component {
  renderMaster() {
    return (
      <UserList contacts={data.results} />
    );
  }
  
  //...
}
  1. Let's create a new UserList folder. Inside this folder, we need to create the index.js and styles.js files for the new component. The first thing we need to do is import the dependencies into the new index.js, create the UserList class, and export it as the default:
import React, { Component } from 'react';
import {
  StyleSheet,
  View,
  Text,
  ListView,
  Image,
  TouchableOpacity,
} from 'react-native';
import styles from './styles';

export default class UserList extends Component {
 // Defined in the following steps 
}
  1. We've already covered how to create a list. If you are not clear on how the ListView component works, read the Displaying a list of items recipe in Chapter 2, Creating a Simple React Native App. In the constructor of the class, we will create the dataSource and then add it to the state:
export default class UserList extends Component {
  constructor(properties) {
    super(properties);
    const dataSource = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2
    });

    this.state = {
      dataSource: dataSource.cloneWithRows(properties.contacts),
    };
  }
  
  //...
}
  1. The render method also follows the same pattern introduced in the ListView recipe, Displaying a list of items, from Chapter 2, Creating a Simple React Native App:
render() {
return (
<View style={styles.main}>
<Text style={styles.toolbar}>
My contacts!
</Text>
<ListView dataSource={this.state.dataSource}
renderRow={this.renderContact}
style={styles.main} />
</View> );
}
  1. As you can see, we need to define the renderContact method to render each of the rows. We are using the TouchableOpacity component as the main wrapper, which will allow us to use a callback function to perform some actions when a list item is pressed. For now, we are not doing anything when the button is pressed. We will learn more about communicating between components using Redux in Chapter 9, Implementing Redux:
        renderContact = (contact) => { 
          return ( 
            <TouchableOpacity style={styles.row}> 
              <Image source={{uri: `${contact.picture.large}`}} style=
{styles.img} /> <View style={styles.info}> <Text style={styles.name}> {this.capitalize(contact.name.first)} {this.capitalize(contact.name.last)} </Text> <Text style={styles.phone}>{contact.phone}</Text> </View> </TouchableOpacity> ); }
  1. We don't have a way to capitalize the texts using styles, so we need to use JavaScript for that. The capitalize function is quite simple, and sets the first letter of the given string to uppercase:
  capitalize(value) {
    return value[0].toUpperCase() + value.substring(1);
  }
  1. We are almost done with this component. All that's left are the styles. Let's open the /UserList/styles.js file and add styles for the main container and the toolbar:
import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  main: {
    flex: 1,
    backgroundColor: '#dde6e9',
  },
  toolbar: {
    backgroundColor: '#2989dd',
    color: '#fff',
paddingTop: 50, padding: 20, textAlign: 'center', fontSize: 20, },
// Remaining styles added in next step.
});
  1. Now, for each row, we want to render the image of each contact on the left, and the contact's name and phone number on the right:
  row: {
    flexDirection: 'row',
    padding: 10,
  },
  img: {
    width: 70,
    height: 70,
    borderRadius: 35,
  },
  info: {
    marginLeft: 10,
  },
  name: {
    color: '#333',
    fontSize: 22,
    fontWeight: 'bold',
  },
  phone: {
    color: '#aaa',
    fontSize: 16,
  },
  1. Let's switch over to the App.js file and remove the paddingTop property we used for making text legible in step 7; the line to be removed is shown in bold:
const styles = StyleSheet.create({
  content: {
paddingTop: 40, flex: 1, flexDirection: 'row', }, });
  1. If we try to run our app, we should be able to see a really nice list on the phone as well as the tablet, and the same component on the two different devices:
  1. We are already displaying two different layouts based on the current device! Now we need to work on the UserDetail view, which will show the selected contact. Let's open App.js, import the UserDetail views, and update the renderDetail method, as follows:
import UserDetail from './UserDetail';

export default class App extends Component {
  renderMaster() {
    return (
      <UserList contacts={data.results} />
    );
  }

  renderDetail() {
    if (Device.isTablet()) {
      return (
        <UserDetail contact={data.results[0]} />
      );
    }
  }
}
As mentioned earlier, in this recipe, we are not focusing on sending data from one component to another, but instead on rendering a different layout in tablets and phones. Therefore, we will always send the first record to the user details view for this recipe.
  1. To make things simple and to make the recipe as short as possible, for the user details view, we will only display a toolbar and some text showing the first and last name of the given record. We are going to use a stateless component here:
import React from 'react';
import {
  View,
  Text,
} from 'react-native';
import styles from './styles';

const UserList = ({ contact }) => (
  <View style={styles.main}>
    <Text style={styles.toolbar}>Details should go here!</Text>
    <Text>
      This is the detail view:{contact.name.first} {contact.name.last}
    </Text>
  </View>
);

export default UserList;
  1. Finally, we need to style this component. We want to assign three-quarters of the screen to the details page and one-quarter to the master list. This can be done easily by using flexbox. Since the UserList component has a flex property of 1, we can set the flex property of UserDetail to 3, allowing UserDetail to take up 75% of the screen. Here are the styles we'll add to the /UserDetail/styles.js file:
import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  main: {
    flex: 3,
    backgroundColor: '#f0f3f4',
  },
  toolbar: {
    backgroundColor: '#2989dd',
    color: '#fff',
    paddingTop: 50,
    padding: 20,
    textAlign: 'center',
    fontSize: 20,
  },
});

export default styles;
  1. If we try to run our app again, we will see that on the tablet, it will render a nice layout showing both the list view and the detail view, while on the phone it only shows the list of contacts: