Building multiple versions of a React Native app

After weeks of caffeine fueled coding, your React Native project is slowly taking shape and is now ready for some real user testing. Suddenly you find yourself needing to create different builds for each environment and tester group combinations. While manually changing settings and replacing files works, it’s often very time consuming and error prone, one forgotten step can lead to many hours of hair pulling. This is clearly unacceptable and we demand a better method!

Here is where iOS Build Configurations and Android Build Variants comes in. Together they will allow you to install different versions of your app on the same device, all at the same time. Each with their own custom settings, display name and icons.

Let’s start with our iOS version.

1. Adding new configurations

Select your project under PROJECT in XCode and in the Info tab, search for Configurations. In there you should have two default configurations — Debug and Release. Click on the + button and duplicate both, name them Beta.Debug and Beta.Release respectively.

2. Adding new schemes

Click on your current active scheme and from the drop down select Edit Scheme, this will open the scheme management window.

Click on the Duplicate Scheme button at the bottom of the window to make a copy of your current scheme, name it Beta. Don’t close this window as we are not done yet! Notice on the left panel we have a few run/build options? Change the Build Configuration of Run, Test and Analyze to use the Beta.Debug we created earlier. Change Profile and Archive to Beta.Release.

3. Different display names

Select your Target and in the Build Settings tab, search for Product Name. For both Beta.Debug and Beta.Release, change the product name to BusDue Beta.

In the project’s info.plist, change the value of Bundle display name to $(PRODUCT_NAME)

4. React Native Schemes Manager to the rescue!

Depending on your React Native version(this guide is using v0.45.1), if you try to run the Beta scheme now with cmd+R, it might fail with a Lexical or Preprocessor Issue.

This reason for this error is because the dependency libraries doesn’t have the build configurations we just created. To fix this problem, we will enlist the help of an amazing library called React Native Scheme Manager by Kevin Brown and Thinkmill.

Setup React Native Schemes Manager

  1. Add the library to the project.
yarn add --dev react-native-schemes-manager
  1. Setup package.json according to library documentations. The two main additions is the postinstall script and a new xcodeSchemes section.
{
"name": "BusDue",
"version": "0.0.1",
"scripts": {
"postinstall": "react-native-schemes-manager all"
},
"xcodeSchemes": {
"Debug": [
"Beta.Debug"
],
"Release": [
"Beta.Release"
],
"projectDirectory": "iOS"
},
"projectDirectory": "iOS"
}
}
  1. Clean the project in Xcode with Cmd+Shift+Alt+K.
  2. Manually run the fix scripts once. `yarn run postinstall`
  3. Run the project again and it should now build without any errors.

5. Running multiple configurations side by side

You might have noticed that if you run the original scheme then run the new Beta scheme, the two versions will replace each other. To be able to run multiple configurations side by side, we will need to set a unique bundle ID for each of them.

Select your Target and in the Build Settings tab, click on the + button and select Add User-Defined Settings to create a new user setting, give it the name BUNDLE_ID_SUFFIX. Add the suffix .beta to Beta.Debug and Beta.Release.

In the project’s info.plist, change the value of Bundle Identifier to $(PRODUCT_BUNDLE_IDENTIFIER)$(BUNDLE_ID_SUFFIX)

You should now be able to have both versions installed at the same time.

6. Different icons for different configurations

To be able to quickly distinguish the different versions at a glance, you might want to use separate icons for each configuration. You can generate your icons for free at https://makeappicon.com.

In Xcode create a new Icon Set, name it Appicon.beta

Copy and paste all the Beta version icons assets to ios/<project_name>/Images.xcassets/AppIcon.beta.appiconset

Select your Target and in the Build Settings tab, locate the Asset Catalog App Icon Set Name setting. Change the values of Beta.Debug and Beta.Release to AppIcon$(BUNDLE_ID_SUFFIX)

Run the app again and each version should now be using it’s own icons

7. Bug alert!

Trying to run the app using a scheme via the command line will not work properly. This is a known bug and hopefully it will get fixed soon. In the mean time, just run the project directly from Xcode.

And this concludes the iOS part of our tour. Next stop Android!

Android

The Android equivalent of iOS’s Build Configuration is called Build Variant. A Build Variant is the combination of a Build Type and a Product Flavor. By default our React Native project already has a Debug and a Release Build Type so we don’t need to do anything here. We will be creating two new Product Flavors — dev and beta. (note: once you add Product Flavors to your app, you will no longer be able to build a flavorless version)

1. Adding Product Flavors

Open your project’s android/app/build.gradle file, add a new section called productFlavors and put our flavor specific configuration here. Use a different applicationId for each flavor so we can have them all install on the device at the same time.

android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {...}
splits {...}
buildTypes {...}
productFlavors {
dev {
minSdkVersion 16
applicationId 'com.myapp.dev'
targetSdkVersion 22
resValue "string", "build_config_package", "com.myapp"
}
beta {
minSdkVersion 16
applicationId 'com.busdue.beta'
targetSdkVersion 22
resValue "string", "build_config_package", "com.myapp"
}
}
applicationVariants.all {...}
}

2. Custom icons for each build variant

Go to your app’s android/app/src folder and in there you should already have a folder called main. Let’s create two more folders here, one for each of our product flavors.

Place any flavor specific icons inside these folders. Take a look inside the main folder if you need a reminder of the folder path for each type of app resources. The way this work is that when you build your app, the build process will first try to find the resources inside a folder that matches the name of the flavor. If it can’t find one, it will fall back to main.

3. Different app names for each build variant

Let’s say we want to change the display name of our beta build to BusDue Beta. We can achieve this by adding a strings.xml to android/app/src/beta/res/values

<resources>
<string name="app_name">BusDue Beta</string>
</resources>

Please note that the contents of strings.xml will be merged with the strings.xml inside the main folder instead of replacing it.

4. Running and building the Build Variants

We should now be able to run our Android project using a specific flavor by passing in a variant argument

— variant=<productFlavour><BuildType>

So to run the our dev version in debug mode, we will use

react-native run-android — variant=devDebug

And to build the release version, we use the command

assemble<ProductFlavour><BuildType>

Example for building a beta release

cd android && ./gradlew assembleBetaRelease

5. Bug alert!

There is a known bug where after adding product flavors, the run command will no longer automatically start the main activity. A simple quick fix is to just manually start the app on the device/simulator.

Error type 3
Error: Activity class {com.mmm/com.busdue.MainActivity} does not exist.

That’s all folks!

I hope you find this guide useful. If you have any questions or suggestions please leave a comment below. Thank you very much for reading and happy coding!

*Special thanks to Circle Engineering for their awesome iOS tutorial, a large portion of the iOS steps in this guide is based on what I learned from them.