Native Modules

What are native modules?

So far in this book we've written all of our apps purely in JavaScript. We've used the built-in React Native components and APIs to interact with the underlying native iOS and Android platforms.

However, sometimes we want to use native functionality that isn't provided out-of-the-box by React Native. In these cases, we can write native components and APIs ourselves, and expose bindings to use them from JavaScript. In React Native, these bindings are called a "bridge."

Common use cases

Native modules are most commonly used for bridging existing native functionality into JavaScript. Native modules are also occasionally used for performance.

Here are a few of the most common cases:

  • Accessing native platform features that React Native doesn't support out-of-the-box, e.g. payments APIs
  • Exposing components and functionality when adding React Native to an existing native app
  • Using existing iOS and Android libraries, e.g. authentication libraries for a 3rd party service
  • High-performance algorithms like image processing that are usually low-level and multithreaded
  • Extremely high-performance views when running into performance issues with React Native views (this is rare)

When to use native modules

Using native modules should be the exception, rather than the norm. It's generally best to write views, algorithms, and business logic in JavaScript when possible. The JavaScript we write is almost completely cross-platform, and updating to new versions of React Native is usually low effort. Native modules, on the other hand, must be written per platform, and can be time-consuming to update since they depend on both the native platform APIs and React Native's APIs. Additionally, we can't use the convenient Expo preview app once we start working with native code -- we have to "eject" our app (covered later in this chapter) and build it using Xcode and Android Studio.

If we're integrating React Native into an existing app (this is known as a "hybrid" app), it's likely we'll use native modules more frequently, since we'll want to expose the existing components and functionality of our app to React Native. In the short term, it's often faster to bridge existing components than to re-write them in React Native. However, in the long term, it can be better to re-write them -- by migrating components to React Native, we'll only need to maintain a single implementation, and our team will only need knowledge of a single language/platform.

Native modules on npm

When we decide we need a native module, we should first check if there's an existing open source implementation. We'll likely find an npm package for common use cases such as taking photos, playing videos, and displaying maps.

The GitHub organization react-native-community maintains several of the most popular native modules. These modules are very high quality and maintained by React Native core contributors.

It's very important to read the installation instructions, as setup for native modules can vary. Most native modules on npm come with two sets of instructions, one for automatic setup using react-native link, and one for manual setup.

react-native-link

Most of the time, installing a native module consists of 2 steps:

  1. Install the npm package with: yarn add foo
  2. Integrate the native code into your app by running react-native link

Remember, yarn and npm work interchangeably, but you should always stick to one or the other. Because we're using yarn in this book, if you see npm install foo in a package's installation instructions, make sure to run yarn install foo instead!

The command react-native link can often integrate native modules automatically. Library authors can configure the various paths and settings used by this command to integrate their native code.

However, react-native link only handles the most common cases, so many native modules come with custom setup instructions beyond this. Custom setup instructions usually involve manually modifying iOS and Android native code.

Manual setup

If you're building a hybrid app, it's likely your directory structure and code will differ somewhat from the structure expected by react-native link. For this reason, native modules usually include a set of instructions for manually integrating the native code into your app. This generally involves modifying the Xcode and gradle build configurations to compile native libraries that were downloaded by yarn into the node_modules directory.

The react-native link command supports some configuration options by adding an rnpm: { ... } object to your project's package.json. However, documentation is currently non-existent. If you choose to try this, the source code for reading configuration options is currently here.

Building a native module

In this chapter, we'll build an app that displays a native pie chart:

There are a variety of graphing libraries available for React Native already, some of which are written in JavaScript and some of which are native modules. In order to explore how native modules work, however, we'll write a native pie chart module from scratch.

The semantics of native iOS and Android code are outside the scope of this book, so we will primarily copy and paste the native code we need. People without any experience writing native code are often able to bridge simple native modules, so we recommend you attempt to follow along even if you don't have any experience with these platforms.

Building this app will consist of the following steps:

  1. Create a new app using create-react-native-app
  2. Eject the app we just created so that we can modify its native code
  3. Write the pie chart component for both iOS and Android
  4. Create a single PieChart.js that renders the native pie chart component from JavaScript

If you're primarily testing on Android, feel free to skip the Xcode/iOS sections, or vice versa. The project will work correctly on one platform regardless of any native code or development tools for the other platform.

Native development is challenging!

There are a lot of things that can go wrong when developing a native app. Although the code we'll write in this chapter is relatively simple (as native apps go), it's likely you'll run into several challenges along the way, especially if you've never done native development before.

The most challenging issues tend to be related to your development environment or build tools. These can be tricky to debug, since they may be somewhat unique to the setup on your computer. When you encounter an error with a development tool or building the app, the best place to start is with a Google search. This will often reveal a Stack Overflow question or GitHub issue where somebody else in the community had the exact same problem. If you don't find anything useful, we recommend opening a GitHub issue on the React Native github repo. This is the most likely way to have your issue resolved in a timely manner. If that still doesn't work, you're welcome to ask us (the authors) for help (instructions on how to do so are in the introduction chapter), but be aware that it's unlikely we will be able to solve problems related to native app development.

It's not all bad news though! The React Native community is extremely active, and new native modules are added to npm frequently -- writing custom native modules will become less and less common as the ecosystem evolves. These complex challenges with native development are a big reason for React Native's success after all!

Development environment

Before we can get started building the pie chart app, you'll need to set up your development environment. If you haven't done native app development before, it's likely you'll need to download some new software. Building for iOS will require a computer running macOS with Xcode installed. Building for Android can be done on any computer with Android Studio installed. We recommend you set up at least one of these tools before continuing with this chapter.

To set up your development environment, follow the instructions on the "Getting Started" page of the React Native docs site. First click the "Building Projects with Native Code" tab at the top of the page, then select the "Development OS" and "Target OS" you plan to test with.

Follow the instructions for the "Development OS" and "Target OS" of your choice, up until the section "Creating a new application." We'll create a new application in a slightly different way than these docs demonstrate (although both will give the same result).

Initializing the project

Just as we did in the previous chapters, let's create a new app with the following command:

$ create-react-native-app pie-chart --scripts-version 1.14.0

Once this finishes, navigate into the pie-chart directory.

Ejecting

Throughout this book, we've been using Create React Native App (CRNA) to set up new React Native projects. This tool dramatically speeds up the process of creating a new project and previewing it on your mobile device. By using the Expo client app for previewing, CRNA eliminates the need to compile any native code. The Expo client app already contains a compiled version of all of the React Native framework code, so apps written purely in JavaScript can be previewed within the Expo client app by executing a JavaScript code bundle downloaded over the network.

However, as soon as we want to add custom native code, we won't be able to preview our app using the Expo client app. We'll need to build our native code using Xcode and/or Android Studio. CRNA can facilitate converting a pure JavaScript app into an app with both JavaScript and native code through a process called "ejecting". Ejecting a project copies the necessary native build dependencies, configuration files, and scripts into the app's root directory. After ejecting, we'll be able to build custom native code in our app, but we'll no longer be able to use the Expo client app for easy previewing.

Since there's no way to undo an eject, if you're ejecting an existing project, make sure the project is backed up in a version control system. In this case, our pie-chart project is brand new, so backing it up is optional.

From the root of the pie-chart directory, run yarn run eject to eject the project now. Upon running this command, you should see the following prompt:

We didn't find any uses of the Expo SDK in your project, so you should be fine to eject to
"Plain" React Native. (This check isn't very sophisticated, though.)

We strongly recommend that you read this document before you proceed.

Ejecting is permanent! Please be careful with your selection.

? How would you like to eject from create-react-native-app? (Use arrow keys)
❯ React Native: I'd like a regular React Native project.
  ExpoKit: I'll create or log in with an Expo account to use React Native and the Expo SDK.
  Cancel: I'll continue with my current project structure.

There are two different ways we can eject a project: either with or without ExpoKit.

If we're importing any of the Expo APIs from within our app (e.g. import { Constants, MapView, LinearGradient } from 'expo';), then we may want to consider ejecting with ExpoKit (the second option). This allows us to continue using the Expo APIs.

If we're not importing any Expo APIs, then we don't need ExpoKit. That's the case here, so we should choose the first option and eject to a "regular React Native project." The yarn run eject command attempts to identify whether or not we're using any Expo APIs and warn us -- here, the first line of the prompt indicates that our app doesn't use any.

If we decide we want ExpoKit at a later time, we can add it to our app then.

Select the first option, React Native: I'd like a regular React Native project.

The next prompt will ask how you want to name your app:

We have a couple of questions to ask you about how you'd like to name your app:
? What should your app appear as on a user's home screen?

Type "PieChart" and hit enter to proceed.

The last prompt will ask how to name the Xcode and Android Studio project files.

? What should your Android Studio and Xcode projects be called? (piechart)

Type "PieChart" and hit enter to proceed.

The script should then proceed with the ejection. After a few minutes (although it can take 10 or more), you should see the following message:

Ejected successfully!
Please consider letting us know why you ejected in this survey:
  https://goo.gl/forms/iD6pl218r7fn9N0d2

This means the project was ejected successfully!

Since we're just building an example project, don't fill out the survey!

There's another tool for starting a new React Native project called react-native init.

This was the original way to get started with React Native, before create-react-native-app was created.

Using create-react-native-app is convenient, since you don't need to have a full development environment set up for both iOS and Android native apps.

Creating a new app with create-react-native-app then running yarn run eject is more or less equivalent to running react-native init. If you know in advance that you'll need to eject your app to use native modules, then you should consider running react-native init for simplicity. We demonstrated the ejection flow since we've been using create-react-native-app throughout this book.

The "Getting Started" page of the React Native docs demonstrates how to use react-native init. To learn more, continue reading from the "Creating a new application" section we recommended you stop at earlier.

Project structure

Let's take a look at the files in our project directory now:


├── App.js
├── App.test.js
├── README.md
├── android
├── app.json
├── index.js
├── ios
├── node_modules
├── package.json
└── yarn.lock

Most of the files are the same as usual, but the ios and android directories and the index.js file are new.

The ios directory contains an Xcode project and the android directory contains an Android Studio project. From this point on, we'll need to build the project in either Xcode or Android Studio before we're able to preview it in a simulator or on a device.

The index.js file is the "entry point" of our app now -- in other words, it's the first JavaScript file in our app that gets executed when our app launches. Let's look at this file now.