Setting up Multiple Environments on React Native for iOS and Android
Edit 20/01/21: update tutorial for React Native 0.63.4
While working on a side project, I quickly realized I needed to be able to set up multiple targets for the same React Native project. There’s several big advantages to doing that:
- Store your environment variables in separate files
- Multiple instances of your app with different environments can be installed on the same phone (with different bundle identifiers)
- No need to add extra-logic to swap between API urls, constants, keys etc.
There’s some great articles describing how to do that on React Native, but all the ones I could find were written a few years back and things have changed a bit since then. So I’ve decided to give an up-to-date tutorial on the matter.
Before we start
The goal of this tutorial is for you to set up multiple environments on your own React Native app. However, if you want to follow along for the sake of learning I’ve created this repository which you can clone on your machine.
Install react-native-config
We are going to use the package react-native-config to handle our environment variables. In your repo, run:
npm install react-native-config --save
You then need to link the library, even if you are using Cocoapods:
npx react-native link react-native-config
If you’re using Cocoapods, you also need to run a pod install:
cd ios
pod install
cd ../
We will then create the files that will store our environment variables. We’ll set two environments: development
and production
, so we create two files at the root of our project: .env.development
and .env.production
.
For the sake of this tutorial, we’ll only store one variable in each file in order to know in what environment our app is running.
In .env.development
:
ENVIRONMENT=development
And in .env.production
:
ENVIRONMENT=production
In a real app, these files would contain all your API urls, constants, API keys… anything that is specific to each environment.
Setting up build types on Android
We will be using build types with suffixes to create different versions of our app on Android.
- Open
android/app/build.gradle
- At the very top of the file, we’ll assign an environment file to each build type:
project.ext.envConfigFiles = [
release: ".env.production",
debug: ".env.development",
developmentrelease: ".env.development",
]
The naming of your build types is important! Make sure to use only lowercase letters with no special characters and append “release” at the end of the name of your custom build types, or they might not work.
- Add this line after
apply plugin
:
apply from: project(":react-native-config").projectDir.getPath() + "/dotenv.gradle"
- Since we’ll use suffixes, we need to edit our default config:
defaultConfig {
...
resValue "string", "build_config_package", "com.myapp"
manifestPlaceholders = [label: 'MyApp']
}
Replace
com.myapp
with the bundle identifier of your app.Note we also added a
manifestPlaceholders
property. We will use thelabel
value it contains to change the name of the app depending on the environment it’s running in. More on that later.
- Add
developmentrelease
build type with the".development"
suffix:
buildTypes {
...
developmentrelease {
initWith debug
applicationIdSuffix ".development"
manifestPlaceholders = [label: 'MyApp Dev']
}
}
- Add matching fallbacks to
debug
build type:
debug {
...
matchingFallbacks = ['debug', 'release']
}
That’s it! Your environment variables are now available to your native code, as well as your Javascript code:
import Config from 'react-native-config';Config.ENVIRONMENT; // "development" or "production"
If you are following this tutorial using the repository I gave earlier, add this line at the top of the App.js
file:
import Config from 'react-native-config';
And change line 21 to:
const environment = Config.ENVIRONMENT;
You can now run both versions of the app on your device.
Development:
npx react-native run-android --variant=developmentrelease
Production:
npx react-native run-android --variant=release
Adding a target on iOS
We will be using targets to handle our environments on iOS. The configuration is a bit more daunting than for Android, so hold tight!
- Open the
.xcworkspace
file of your app in Xcode, then right click on your project >New Group
- Rename this group to
MyAppDevelopment
(replacing “MyApp” with the name of your app) - Right click on your main target (
MyApp
for me) >Duplicate
>Duplicate Only
- Rename the duplicated target to
MyAppDevelopment
- In the
General
, set theBundle Identifier
tocom.myapp.development
. By having a different bundle identifier, we’ll be able to install the two versions of our app (development and production) on the same device.
- Move the new
Info.plist
file that got automatically created when you duplicated the target intoMyAppDevelopment
group, then rename it toInfo.plist
. Note that you’ll need to actually copy and paste the file intoMyAppDevelopment
group, if you just slide it from Xcode only the reference will be moved, not the actual file! - If your main target contains a
.entitlements
file (MyApp.entitlements
), duplicate it and move it insideMyAppDevelopment
group, then rename it toMyAppDevelopment.entitlements
. This will permit you to support different capabilities for each target (remote notifications, app groups…). - In the
Build Settings
tab ofMyAppDevelopment
target, search for thePackaging
section then setInfo.plist File
to the path of theInfo.plist
we just moved;MyAppDevelopment/Info.plist
in our case.
If in the previous step you duplicated a
.entitlements
file, you’ll also need to search for the sectionSigning
and setCode Signing Entitlements
to the path of the.entitlements file
(MyAppDevelopment/MyAppDevelopment.entitlements
).
- Click on
MyApp
in the left corner of the top bar >Manage Schemes
and scroll down to find the newly created schemeMyApp copy
. Rename it toMyAppDevelopment
, then click onEdit
in the bottom left corner. - Click on
Build
accordion (without expanding it), then on the+
in the bottom left corner. Search for “React” and add the item namedReact
(dah). Make sure to untickParallelize build
.
Our new target should now be able to run, but we need to configure react-native-config
.
- Expand
Build
accordion, click onPre-actions
then on the+
in the bottom left corner >New Run Script Action
. - Replace the content of the script with:
echo ".env.development" > /tmp/envfile
- Repeat the same action for the main target, this time replacing the content of the script with:
echo ".env.production" > /tmp/envfile
We’re done playing with Xcode! We’ll now edit the Podfile
of the project (ios/Podfile
).
- Since you’re reading this tutorial, chances are your project is articulated around one main target. We’ll first need to set an abstract target, so multiple targets can use the same pods and also use their own pods. In the
Podfile
, replace the line:
target 'MyApp' do
With:
abstract_target 'App' do
App
can be replaced with anything you want, but shouldn’t be the same as any of your targets’ names!
- We now need to define our targets, that will inherit from our abstract target. Right bellow the last
pod ‘SOME_PLUGIN’, :path => ‘../node_modules/SOME_PLUGIN’
(wereSOME_PLUGIN
will probably bereact-native-config
since that’s the last plugin we installed), add our two targets:
target 'MyApp' do
#Production-specific pods target 'MyAppTests' do
inherit! :complete
# Pods for testing
end
endtarget 'MyAppDevelopment' do
#Development-specific pods
end
Note: we moved the default test target
MyAppTests
insideMyApp
to respect the original architecture.
If you used the repository I linked at the beginning of this tutorial, your Podfile
should look like this:
- Finally, if you use Cocoapods you’ll need to run pod install again:
cd ios
pod install
cd ../
That’s it! Your environment variables are now available to your native code, as well as your Javascript code.
If you want to access your environment variables from your
Info.plist
files, follow these extra steps for each scheme.
You can now run both versions of the app on your device.
Development:
npx react-native run-ios --scheme "MyAppDevelopment"
Production:
npx react-native run-ios
I hope this tutorial was helpful to you. If you’d like to see the codebase on Github with all the environments set up, here it is.
Feel free to ask me any questions you have by commenting on this post. Have fun!