Taxonomy Icon

Mobile Development

One of the popular frameworks for cross-platform mobile application development is React Native. This article provides an overview to React Native, with pointers to existing resources and some suggested best practices for writing your own React Native mobile app.

When you develop mobile apps, you have the choice of going full native, pure web-based, or using a hybrid approach (that is, using a combination of native and web-based technologies). You can write cross-platform hybrid apps in HTML, CSS, and JavaScript using open source platforms like Apache Cordova (or Adobe PhoneGap). These are apps that run inside a native web view (browser) and behave more like web apps but have access to native functionality and APIs by using JavaScript and the available Apache Cordova plug-ins. The result is apps that you can deploy to the Apple Store or Google Play with a user interface that resembles a web app more than a native app. Or, you can write native apps with Xamarin using C#, XAML, and CSS. For pure non-native mobile web apps written in HTML5, CSS, and JavaScript, you can use frameworks like jQuery Mobile.

React Native takes a different approach to hybrid mobile app development. Based on React, React Native is a JavaScript library for building web-based user interfaces that instead generate native UI components, resulting in a richer UI experience that better matches the underlying platform’s UI. React Native has become a popular technology that offers a strong framework for building native cross-platform mobile apps using JavaScript, but you can also write native code using Objective-C, Swift, or Java when needed.

There are pros and cons to using React Native. On the positive side, React Native has become popular with a supporting open source community, and you can build mobile applications for iOS and Android using one set of technologies, such as JSX, React Native components, and JavaScript. This in turn helps keep your iOS and Android apps pretty much in sync when releasing new versions. However, React Native is still evolving, and it can be confusing when you try to decide which libraries to use when you need functionality that is not provided by React Native today, such as how to do UI navigation or use maps. Also, depending on the complexity of your app, you still might have to write platform-specific code to tackle mobile platform differences. For complex applications, you might have to write custom components or get deep into iOS and Android (for example, for needed but unsupported UI behavior, for performance reasons, or when adding React Native to an existing native app).

Getting ready for React Native development

At the heart of React Native is JavaScript, specifically ECMAScript 6 (ES6). So, it would help for you be familiar with some of the latest features of ES6. Also, before you can dive in to developing your first React Native app, you need to install tools, IDEs, JavaScript libraries, and more. Finally, you need to be aware of the core React Native APIs.

ECMAScript 6 features

ES6 introduces a number of changes, some of which are worth mentioning because they are used throughout the latest React Native framework:

  • Variables and scoping: The keywords var, let, and const indicate variable scope, where var is function-scoped, let is block-scoped, and const is also block-scoped. const variables are constant except for objects and arrays, which are mutable in JavaScript. The interpreter hoists or moves variable declarations to the top of the scope for var declarations, whereas let and const declarations are not hoisted.

  • Arrow functions: A new type of function that allows for a more compact, less verbose notation and with a definition of this that is static or lexical (it doesn’t lexically shadow surrounding this). See these examples of using arrow functions.

An arrow function with no parameter specified:

() => { ... }

One parameter; notice the omitted parentheses:

x => { ... }

Several parameters specified:

(x, y) => {* 
...
}

Parameter definitions and the arrow of an arrow function must be in the same line.

  • Promises: Promises are an alternative API for asynchronous programming that provide benefits over traditional callbacks such as chaining and parallel executions.

      function myAsyncFunc() {
        return new Promise(
        function (resolve, reject) {
              :
        resolve(result);
               :
               if (error) {
                    reject(error);
               }
        });
      }
    

    To call the previous asynchronous function:

      myAsyncFunc().then(result =\> { B7B7B7 }).catch(error =\> { B7B7B7 });
    

This is only a sampling of the new features, but there are many others. A great online reference on ES6 is Exploring ES6 by Axel Rauschmayer.

Next, let’s look at setting up your development platform and environment.

Setting up your environment and project

A macOS computer is required to develop iOS native apps — there is no workaround. Because most mobile apps are deployed to both Android and iOS, you should consider getting a Mac for developing React Native mobile apps.

Before creating a project, you need to install a number of things, including:

  • Both Android Studio and Xcode tools. Install the latest versions to build and release your apps. For Android, make sure that you set up a virtual device simulator for an Android API version that you want to run against.

  • Your favorite JavaScript IDE or Text Editor. You probably won’t be using Xcode or Android Studio for writing your JavaScript code. All you really need is a text editor. You can use Atom (developed by GitHub), Sublime Text, or any good text editor of your preference.

  • JavaScript or React Native packages. As part of this step, you install a number of tools to manage React Native packages as well as third-party libraries:

    • Download node.js from nodejs.org. This JavaScript runtime gives you access to npm, which is a convenient tool created by the node.js project that you can use to manage open source packages. Make sure that you download the latest LTS (Long Term Support) version of node.js. Also included with this download is a development server called the Metro bundler, which provides live updates when debugging.

    • Download create-react-native-app. You can use this tool to get started. It creates a base project for you. Using npm, download the package:

      sudo npm install -g create-react-native-app
      
    • Download third-party libraries. React Native provides foundational components, but it is pretty plain when compared to native APIs. A typical mobile app uses the camera, manages state, and has navigation, maps, icons, and check boxes. You need to get these components from the React Native community:

      • react-native-navigation
      • react-native-vector-icons
      • redux
      • React-native-maps

      Also, go to React Native Elements for other UI elements that you might need. These packages will become necessary when you develop full functionality apps. Install the previous packages by running:

      npm install _package-name_ --save
      
  • React Native Debugger. This debugger is a stand-alone app that includes React Inspector and Redux DevTools. It is a nice tool to connect to your app (one app at a time) to see and evaluate your React Native app in real-time.

Figure 1. React Native Debugger

Now, you need to set up the app base project and run the base app:

  1. Create the app base project. This creates a directory structure, a starting point for your project:

    create-react-native-app [APPNAME]
    
    cd [APPNAME]
    

    Note: This step creates a base React Native app template with no build configuration. Read more about the create-react-native-app project in its GitHub project.

    After installation, a number of npm scripts are available:

    • npm start to run the app in development mode
    • npm run ios, which is like npm start, but also tries to open your app in the iOS Simulator
    • npm run android tries to open your app on a connected Android device or emulator
    • npm run eject ejects your app from the current create-react-native mode, which allows you to take full control of the app build process
  2. Start the app. Run the app on the simulators by running npm run android and npm run iOS. This starts the app in development mode. It also starts the Metro bundler, which detects changes and deploys your app automatically (this is great when you are debugging mobile apps).

Figure 2. Metro bundler

With this, you should have most of what you need to build your mobile app.

Quick overview of React Native APIs

From React, React Native inherits the concepts of JSX, state, props, and component lifecycle. It then extends React by providing support for native UI components and functionalities. This is accomplished by importing React and React Native functionality, as shown in Listing 1:

Listing 1. Importing from React and React Native

import React, { Component } from **'react'**

import { View, ScrollView, StyleSheet } from **'react-native**'

:

:

Once imported, you have access to React Native components that support many of the common UI components that you need for your app:

One thing to keep in mind is that there is a lot of functionality that is not supported out of the box by React Native. One example is using maps, or even icons and check boxes. While these are supported by the underlying OS, React Native requires the installation of third-party libraries. In short, anything complex or not supported by React Native (this includes development tools) requires that you write additional code or use a third-party library.

Building applications with React Native

A typical React Native mobile application consists of a number of components, as illustrated in Figure 3:

Figure 3. Typical React Native mobile application components

In an app container, you have a number of screens that consist of one or many views (and styles), interscreen navigation, state management, and data models. There is also local storage and network functionality to consume services on the cloud. Last but not least, there are third-party libraries to expand your app’s functionality.

The following sections describe each of these areas.

Screens: Views and styles

The collection of screens and other components of the app are contained in an app container. The app itself consists of many screens, and each screen consists of a number of views such as buttons, text inputs, and lists. Views are the most fundamental component for building a user interface, and views typically map to the iOS and Android native View components.

Listing 2 is an example of a screen that consists of a list, implemented using a ScrollView, and a list of tasks encapsulated in a Checkbox UI component:

Listing 2. TaskList with ScrollView

# components/TaskList/TaskList.js

import React, { Component } from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import { CheckBox } from 'react-native-elements'

import styles from './TaskListStyle;

export default class TaskList extends Component {

    renderTaskItem = (item, idx) => {

        return (
            <View style={styles.row}>
                <Checkbox
                    checked = {item.checked}
                    title = {item.text}
                    onPress = { () => onPressItem(item, idx) }
                />

            </View>
        );
    }

  render() {
    const {list} = this.props
    return (
        <ScrollView style={styles.container}>
            {list.map( this.renderTaskItem )}
        </ScrollView>
    )
  }
}

The TaskList component imports the various dependencies, including a third-party Checkbox UI component not found in React Native, which you can download from the React Native Elements website.

In Listing 2, the UI component and the styling are separated. I recommend that you create high-level UI components and separate the styling. For this, you would organize all your components in a subdirectory, with each component having its component.js and style.js files as shown in Listing 3:

Listing 3. Organizing components and styles

./Components
+-TaskList
  +-TaskList.js
  +-TaskListStyle.js

Or, if you prefer a more generic approach:

+-TaskList
  +-index.js
  +-style.js

This approach allows for clean separation between component and styles and allows for a more compact component. Note that when sharing or reusing, it is a good practice to share the component itself and avoid sharing stand-alone styles.

Listing 4 shows an example of a style file:

Listing 4. Styling

# components/TaskList/TaskListStyle.js

import { StyleSheet } from 'react-native';

// Define the styles
export default StyleSheet.create({
    container: {
        flex: 1,
        paddingLeft: 20,
      },

      row: {
        flex: 1,
        flexDirection: 'row',
        alignItems: 'flex-start',        
      },
});

Styles are defined using the StyleSheet component, which is an abstraction similar to CSS stylesheets. Reference the individual UI component for specific styling properties.

Apply adaptive design to your UI

React Native provides a Dimensions module to detect the dimensions of the screen in real-time, for example:

Listing 5. Using the Dimensions API

import { Dimensions } from 'react-native'
:
var {height, width} = Dimensions.get('window');
:

The Dimensions API is useful in adapting your UI in real-time as needed; for example, you can adjust your UI when the orientation changes by applying styling as appropriate. Examples of adapting the UI include changing the position and length of text input fields for landscape versus portrait orientation.

There is another level of UI adaptation that you must be prepared to handle. You will encounter situations where you will need to implement behavior that is particular to Android or iOS. Examples include differences related to underlying behavior such as how a UI control renders or behaves in general. To solve this design issue, you need to apply different logic or styles, or in more complex cases, implement custom components. React Native provides different methods to detect the underlying platform so that you can programmatically decide what to do: either using the Platform module or using platform-specific file extensions.

The Platform module detects the underlying platform and returns “ios” or “android” accordingly. For example:

Listing 6. Using the Platform module

import {Platform} from 'react-native';

:

if (Platform.OS === 'ios') {
    :
    :    
}

if (Platform.OS === 'android') {
    :
    :    
}

The Platform module also provides Platform.select and Platform.Version to detect the platform and platform-version respectively. For more information, see the Platform documentation. You should use Platform.OS and Platform.select only for small changes. Otherwise, you will end-up with a lot of hardcoded if or switch statements, which makes your code harder to follow.

If you are writing more complex code or components, you should use the platform-specific file extensions. In this approach, you would split your code using per-platform files. That is, if you have code that cannot be made common across platforms, let’s say, our TaskList, you would then write two files, one for iOS and one for Android, and then let React Native pick up the right one.

Listing 7. Organizing components and styles

./Components
+-TaskList
  +-TaskList.io.js
  +-TaskList.android.js
  +-TaskListStyle.ios.js
  +-TaskListStyle.android.js

Then, you let React Native pick up the right file:

const TaskList = require('./Components/TaskList/TaskList);

What exactly would be different between iOS and Android is not covered in this article, but things to watch out for include rendering and general component or UI control behavior. Now, if it turns out that the layout designs for your Android versus iOS app are different in major ways, you can control this by using index.ios.jsb and index.android.jsb files respectively instead of index.js for your main app. This gives you total control of the app layouts and flow for the different platforms. To minimize duplicated code, you need to repackage it into reusable helpers as necessary.

App navigation in React Native is one area that can be challenging because the navigation provided by React Native is either not powerful-enough, or only targets iOS. Instead, there are a large number of community projects trying to address cross-platform navigation, with some projects more popular than others. Covering each available project is outside the scope of this article, so I’ll just focus on the popular React Native Navigation by Wix.

React Native Navigation provides a pretty complete API that provides a top-level API to register screen components, start tab-based apps, and to start single screen apps. It provides other APIs to manage modal and lightbox screens, as well as lower-level screen APIs to handle the screen stack, handle buttons, screen visibility, and the ability to customize the navigator itself.

Install the latest stable version of react-native-navigation (run npm install react-native-navigation --save) and follow the Android and iOS installation instructions on their website that require you to modify the build scripts or values.

You must register all screen components with Navigator with a unique name by calling registerComponent(). Once registered, you can start your tab-based app or your single screen app, as shown in Listing 8:

Listing 8. React Native Navigation

import { Navigation } from "react-native-navigation";
import MainScreen from "./src/screens/MainScreen/MainScreen";
:

// register the MainScreen component
Navigation.registerComponent(
  'MainScreen',
  () => 'MainScreen'
);

:
:

// Start the (single screen)App
export default () => Navigation.startSingleScreenApp({
  screen: {
    screen: "MainScreen",
    title: "Main Screen"
  }
});

Starting a tab-based screen is a bit more involved, as you must indicate the different tabs and their associated screen, the tab details and styles, for example:

Listing 9. Start tab-based app

// Start the (tab-based) App
export default () => Navigation.startTabBasedApp({
    tabs: [
        {
            screen: "screen unique name",
            label: "label",
            title: "title",
            icon: icons[0],
            navigatorButtons: {
                leftButtons: [
                    {
                        icon: icons[2],
                        title: "title",
                        id: "unique id"
                    }
                ]
            }
        },
        {
            screen: "screen unique name",
            label: "label",
            title: "title",
            icon: icons[1],
            navigatorButtons: {
                leftButtons: [
                    {
                        icon: icons[2],
                        title: "title",
                        id: "unique id"
                    }
                ]
            }
        }
    ],
    tabsStyle: {
        tabBarSelectedButtonColor: "black"
    },
    drawer: {
        left: {
            screen: "drawer screen unique name"
        }
    },
    appStyle: {
        tabBarSelectedButtonColor: "red"
    },
});

You can switch between single and tab-based screens as needed by invoking the corresponding functions. Also, note the use of drawer in the previous example. You can define a drawer side menu for both single screen and tab-based apps.

State Management

In React (and React Native), props and state are used to control a component. props (properties) are the parameters used to customize components when they are created. For example, the Button components provide support for a number of properties; in Listing 10, title and onPress are props.

Listing 10. Using state

<Button
  onPress={onPressHandler}
  title="Learn More"
  :
/>

Properties are set up by React itself (set by the parent Component) and remain fixed throughout the lifetime of the component. State is app-specific data that changes as needed to drive component behavior. State should be initialized in the constructor and only changed by calling setState. Listing 11 shows the use of state:

Listing 11. Using state

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {isActive: true};
  }

  toggleActiveState() {
      this.setState(previousState => {
          return { isActive: !previousState.isActive };
      });
  }

  render() {
      :
  }

}

Listing 11 is an example of simple state management, but if your application is more complex with many screen components and intercomponent state management, you should leverage Redux. A more advanced approach to state management, Redux provides a framework for implementing state management and related actions and handlers. But using Redux adds complexity to your app, so you should consider using it only when you really need it (that is, for larger complex apps).

Local Storage

React Native provides support for local storage with AsyncStorage, a simple to use, basic key-value asynchronous (persisted) storage system that is global to the whole app. It is simple, as it requires no major setup. On iOS, it is implemented as a serialized dictionary or as separate files. On Android, it is based on SQLite or RocksDB.

Listing 12. Local storage with AsyncStorage

import {AsyncStorage} from 'react-native'
:

const USERNAMES_KEY = 'Usernames'

const user = {
    name: 'John',
    last: 'Smith',
    username: 'jsmith'
}

// Storing the item
AsyncStorage.setItem(USERNAMES_KEY, JSON.stringify(user))

:

// Get item promise to retrieve the item
AsyncStorage.getItem(USERNAMES_KEY).then((item) => {
    const userItem = JSON.parse(item)
})

:

But AsyncStorage has a number of limitations, such as slow performance, no indexing, and no encryption. Let me repeat that: It is not encrypted. If you are storing large amounts of data and performance and encryption are important factors for your application, you should consider using alternatives like Realm React Native. Realm React Native is cross-platform, performs better, supports encryption, and even comes with its own version of ListView (with the same API signature as React Native ListView) that is optimized on top of Realm, drastically improving access to the local store. You can open and view .realm files for debugging with Realm Browser. Realm uses a static schema definition approach to improve performance.

Listing 13. Local storage with Realm React Native

class Usernames {
    static schema = {
        name: 'User',
        properties: {
            name: 'string',
            last: 'string',
            username: 'string',
        }
    }
}

let realm = new Realm({schema: [Usernames]});

realm.write(() => {
    realm.create('Usernames', { name: 'John', last: "Smit", username : "jsmith" });
});

Network

Mobile applications are typically connected apps. Mobile apps connect to the network to authenticate, to download assets or content, and to post information to the cloud. React Native provides different network APIs:

  • The original Ajax (Asynchronous JavaScript + XML) XMLHttpRequest API.
  • WebSockets, which provides full-duplex communication channels over a TCP connection.
  • Fetch API, the newest member of the family, which is similar to XMLHttpRequest but with additional features.

Listing 14 uses the fetch API to retrieve a small JSON file of usernames from a server.

Listing 14. Using Fetch API for networking

{
  "users": [
    { "name": "John Smith", "username": "jsmith"},
    { "name": "Matt People", "username": "mpeople"},
    { "name": "Graciela Lopez", "username": "glopez"},
    { "name": "Jeff Bezos", "username": "jbezos"},
  ]
}

--

import React from 'react';
import { View, Text, FlatList, ActivityIndicator} from 'react-native';

export default class FetchUsernames extends React.Component {

  constructor(props){
    super(props);
  }

  // dispatch the fetch once the component mounts
  componentDidMount(){
    return fetch('https://...')
      .then((response) => response.json())
      .then((responseJson) => {
this.setState({
          dataSource: responseJson.users,
        }, function(){
        });
      })
      .catch((error) => {
        console.error(error);
      });
  }

  // render the component, populating the FlatList from the 
  // state.dataSource that was in turn populated from the JSON
  render(){
    return(
      <View>
        <FlatList
          data={this.state.dataSource}
          renderItem={({item}) => 
               <Text>{item.name}, {item.username}</Text>}
          keyExtractor={(item, index) => index}
        />
      </View>
    );
  }
}

Some notes about the fetch API and networking in general:

  • Note the use of Promises in Listing 14. These won’t fail (reject) even if the response is an HTTP 404 or 500. In other words, it only rejects on network failures. The way to detect non-network failures is to check the OK status.
  • iOS by default blocks requests that are not encrypted using SSL. The only way to work around this is to add an App Transport Security exception.
  • You can use NetInfo to detect or retrieve network information. It is a useful API to detect online or offline status, connection types (Wi-Fi, cellular), and effective connection type (2G, 3G, and 4G) — and it is cross-platform (for the most part).

Conclusion and next steps

This article provides just a high-level view of React Native. React Native in general is a more complex topic. You should look into each of the areas that I covered in more detail.

React Native is a dynamic and growing framework and community. But React Native is not as mature as native app development. You must keep stable environments for Android, iOS, and React Native, including all related development tools. For components that you need that don’t exist in React Native, do a Google Search and you can find what you need, keeping in mind that the selected third-party library might change or its maintenance might stop or be abandoned, which is a major risk to consider. If you don’t find what you need, go ahead and write the needed components and share them with the rest of the React Native community. Also, keep an eye on Facebook’s React Native blog for related latest news.

React Native is not for everyone or every project. You need to balance the time, effort, and cost of creating and maintaining React Native code, tools, development environments, and skill-sets versus just focusing on native code. There are recent examples of large development organizations, such as Airbnb, that dedicated a lot of effort and investment on React Native but at the end decided to return to pure native development.