Setting up Android product flavors and XCode targets in React Native
An Android Developer’s Roadmap to React Native - Part 4
If you come from native development background, you might be aware of a feature that both Android Studio and XCode provide to create different applications from the same code. These are known as flavors in Android Studio and targets in XCode. The idea is to create different packages with same core functionality but with minor difference in features. This can include different color schemes, branding or some functionalities unique to a package. A common example is creating a free and paid version of a same application, wherein the paid version has same core functionality as the free version with some added functionalities and features. Since the major functionality is the same there is no need to create different projects, but handle flavor/target specific changes at run-time. I hope you get the idea. If not follow these links to learn more.
For simplicity, I will be using the Android term flavors
to represent different product builds in React Native.
Now, lets move to the problem at hand
The project I was working on relies heavily on build flavors, and React Native does not have inbuilt functionality to handle the same. Sure, you can create different flavors from react-native-cli
but that only decides the build process, not the actual run-time changes that need to be made. So here is what I came up with, to bridge this gap between native and React Native.
Setup
Step 1: Configuration ( Native )
The first step is to setup product flavors in Android Studio and build targets in XCode. For the purpose of the tutorial, our application will be called NoteTaker
. This application has two flavors: lite
and pro
. You can read the above mentioned articles to help you setup the configuration on Android Studio and Xcode.
Step 2: Native Modules
Once we have setup the flavors on the native side, we need a way to pass the product flavor identifier from native to react-native at run time. This, definitely requires us to create native modules or bridge methods for both Android and iOS. For simplicity, we will keep our flavor identifiers as strings. In Android, the flavor in stored in the BuildConfig
and for iOS you can use TargetName
from info.plist
Take a look at the following documentation if you do not know what native modules are in React Native.
Step 3: Flavor.js
Next, we create a JavaScript file that serves as a utility class for flavors in React Native. This file exposes a method to get the current product flavor from native and a variable that stores this information once it is fetched from native.
Step 4: Colors.js
Since our lite
and pro
versions have different color schemes, we create a Colors.js
file, that contains the flavor specific colors. Also this file exports a method to get the relevant colors based on the product flavor.
Our setup is now almost complete. We just need to call the build flavor method at the start of our react native application. But there is a catch. Since the build flavor is asynchronous, we need to ensure that the first component is mounted only when the flavor has been fetched from native. For this, we pass null in render method of our component initially, and on receiving callback from buildFlavor
method we re-render the component by using setState
Implementation
Implementing flavor specific features
Now that our flavor has been fetched from native and have access to our product flavor in any component, writing flavor specific code is a piece of cake. In fact, we have already seen such implementation in getThemeColors
method. Consider a feature of adding images to notes which is only available in the pro
flavor
Implementing color schemes
When defining styles for a component you can use two approaches:
1. Directly apply styles using the styles prop
2. Use StyleSheets
The first method poses no problems since the styles are evaluated and applied at run time when that particular component is mounted. Thus the getThemeColors
method is evaluated properly and we get the required colors.
However, when using StyleSheets, the styles are evaluated when the file is loaded into the JavaScript run time environment. This means that even if the component is not mounted its styles are already evaluated. Thus, in our case, the delayed render of the component in App.js
after the flavor is fetched, fails to serve its purpose since even though the component is not mounted, its styles have already been evaluated. Thus, getThemeColors
returns the wrong set of colors. To overcome this, we need to create StyleSheets for the component only when it is going to be mounted. We achieve this by creating StyleSheet in componentWillMount
lifecycle method.
So now we have product flavor specific features and color schemes. The only drawback with this setup is that the styles and color schemes are now generated at run time rather than at compile time. Not that it has much of an impact on performance, but still, is a step behind. If you have any ideas to improve this setup, please feel free to share them with me either here in responses or via e-mail.
This is the fourth part in the series “An Android Developer’s Roadmap to React Native”. You can read previous articles in the series by following these links:
Part 1: https://medium.com/@rohanx96/an-android-developers-roadmap-to-react-native-part-1-5ec20cf93757
Part 2: https://medium.com/@rohanx96/set-up-your-own-react-native-ide-f7d52c90da43
Part 3: https://blog.usejournal.com/redux-an-intro-and-beyond-92d694bc314f