A 12-Factor Approach to Environment-Specific Builds in React Native

Having separate builds for dev, beta, and production environments is critical for most apps.

These environment specific builds give us a way to:

  • Change the values of variables at build time
  • Change app/bundle ids to allow the installation of any environment variant on the same device at the same time
  • Change the icon for each build variant
  • Change the display name of the app

Let’s look at how to do that in React Native!

There are two main ways to configure environment-specific builds in your app, and neither of them are the following approach:

The traditional (manual) approach

The first way to set up environment-specific builds involves using and in Xcode, and or for Android. You then have to set up user-defined variables in Xcode and create separate files for Android. But guess what? You now have to manage the values in both places.

It would be nice to have a single place to do this, and ideally a way to do so without touching native code.

The 12-factor approach

At Echobind, we follow the 12-factor approach for all of our web and mobile apps. Using this approach, you create a single version of your app configured differently via environment variables. We see multiple wins by taking this approach in React Native:

  • It’s harder to accidentally build and submit a release of the wrong type and for example, submit a beta build to the App Store.
  • There aren’t multiple configurations to manage differently (one for iOS and one for Android)
  • If you your files, developers don’t have access to staging or production configurations by default, which increases security.

In React Native, we can gain access to ENV variables by using . is an alternative option, but it doesn’t currently work in the App Center environment. We use App Center on a lot of client projects (expect an App Center specific post from us soon!), so we stick with . It requires a bit more setup, but you also get the added benefit of having access to environment variables in native code, and configuration files if you need them (such as on iOS and on Android).

expects an file which defines all the environment variables used in your app. We strongly believe that files should not be checked into source control, so we needed a way to provide one to our CI server.

.env on CI

Most CI’s support build scripts to run custom commands at various points in the build pipeline. We can leverage these scripts to create a file for us. Here’s what an example script looks like, which creates an dynamically:

This script takes a configurable whitelist of ENV variables (defaulting to anything with an prefix), and uses and egrep--> to filter available ENV variables down to only the variables that match that whitelist regex. The output is saved to an file which react-native-config reads before building our app.

Changing variables at build time

With setup, changing your API server or any other variable is now just a matter of:

  • Using in place of a hardcoded value in your app code.
  • Updating your local file to have a sensible default.
  • Adding an ENV variable to your CI server with the or value.
  • Informing your team members. We like to keep an in the project root that contains all the variables (without real values) that need to be set.

Change the bundle / app id for each environment

Each app has an id assigned to it (typically ). This is called a bundle identifier on iOS and an app id on Android. If the beta build of your app and release build of your app share an id, you can’t install both at the same time on the same device. To get around this, we dynamically assign a different id at build time.

We need a way to add a suffix to the id for each release type:

Tools like PlistBuddy and Gradle scripts can help with this, but we’re trying to do things cross-platform in a unified way. Enter fastlane. To setup fastlane, follow the Getting Started section in the guides.

To start, we’ll create a basic lane that will be used to update the bundle / app id. Create a folder with the following 2 files in the root of your project:

`fastlane init` will complain about finding an ios project in a subfolder, so just create these manually.

Update with the app_identifier and package_name that your app uses. This will avoid prompts from fastlane to request these values.

Next, add the magic. fastlane comes bundled with an ios plugin called that we will use to update the app_id. On Android, we’ll leverage a built-in concept called and install a plugin that does the same thing. Add the following a line to , and set it to an empty string by default.

After setting up react-native-config, this is the only “native” change you’ll have to make

Next, install the plugin:

As of August, 1, 2018, this plugin doesn’t properly update values that contain empty strings (I have an open PR that fixes it). In the meantime, if you want to follow the same process here, you’ll need to point to a fork.

Update the that was created when you installed the plugin to point to the fork:

Run to install the updated gem.

To use these plugins, update your to the following:

In this lane, we provide a configurable release type. We typically use , , and , but additional types can be added by changing the environment variable.

We then set a customizable suffix, which defaults to the release type.

Time to test it out! Run the following command from your project root. The use of is important to ensure we execute the command in the context of our project .

You should see the following updates:

ios changes
android changes

Remember, this is designed to be run on CI and discarded after build, so it’s fine that these values get overwritten.

Change the Icon for Each Environment

Now that we’re able to install the different builds side-by-side we need to make the app icon visually distinctive so we know which one we’re opening.

To do this, we’ll make use of a great plugin for fastlane called badge.

If you don’t already have Imagemagick installed, do that first ( on a mac).

Next install the badge plugin:

Insert an command to the existing lane for each platform. If you have a typical icon setup, the following should just work for you. Otherwise, you may have to adjust the line for each platform.

Now, we can re-run our previous command to produce some slick looking badges!

Example of our beta icon
Example of our alpha icon

Note: Like the app id changes, it will modify the icon in place. If you’re testing this out locally, just discard the changes.

Changing the display name

For iOS, we just have a minor update, but to handle Android we need to install another plugin to update our file:

Now we can update our lane to change the display name on both platforms:

Environment-specific builds for the win

Welcome to a scalable, secure way to set up variables, icons, display names, and bundle suffixes for different environments your app needs. You should be able to set up any CI to run your Fastlane lane, overriding the defaults via ENV variables as needed.

Here’s our final for reference:

Need help with your React Native app?
Say hello! We’d love to chat.

More about:

Chris Ball

Chris is CTO at Echobind, a full-service agency specializing in custom web and mobile apps. When he’s not helping developers grow or creating amazing things for clients, you’re likely to find him playing music, cycling, or camping.