“Before building an actual rocket, one should build a prototype. Before experimenting on the actual rocket, one should experiment on the prototype.” - not elon musk.
Update: react-native-config
breaks with latesst react-native 0.74
Source code for the impatients.
Almost all of the web apps I have ever worked on had some sort of dev/prod parity setup. The larger teams and codebases even had more build types like local, QA, preprod etc. And it does make sense to separate out your main build from the one being actively developed. It is also in conjecture with 12 factor app philosophy.
In the mobile apps world, we follow something similar called build variants. If you’re not aware of it please read this blog first. It explains buildtypes
and productflavors
in detail. To put it in simple words. We have something called Build types, Product flavors cross product of the two gives us Build variants.
💡 BuildTypes * ProductFlavors = BuildVariants
The way it works in react native is also similar, we define buildTypes and productFlavor in app/build.gradle file. BuildTypes usually represent debug and release mode bundle whereas ProductFlavor is further divided into two more axis.
One axis is our environments like local, dev, qa, test, prod, etc. and the other one flavorDimensions, which is optional but useful in apps that have for ex. free and pro versions with almost similar code, or uber like separate apps for the riders and the drivers. You can enable flavors and write conditional code which gets built and compiled based on productflavors and flavorDimensions.
Now that we’re clear about build types and product flavors let’s set it up in a new RN project.
Step 1: Create a new RN project using React-native cli.
npx react-native@0.73.6 init MyApp --version 0.73.6
Step 2: Install react-native-config package
cd MyApp **&&** npm i react-native-config
Step 3: Define config for the build types, product Flavors in app/build.gradle.
-
Add the following lines in the same file in the defaultConfig block. This will be applied to all the
buildVarirants and we can also override these values.
defaultConfig { resValue "string", "build_config_package", "com.myapp" resValue "string", "build_config_package", "com.myapp" applicationId "com.mmadmin" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode project.env.get("VERSION_CODE").toInteger() versionName project.env.get("VERSION_NAME") }
b. You’ll notice buildTypes already exists. You can add/edit the configuration related to the specific bundle for ex. enableHermes option or enable source map. We’ll leave it as it is for now.
buildTypes { debug { signingConfig signingConfigs.debug } release { // Caution! In production, you need to generate your own keystore file. // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } }
c. Let’s now define flavorDimensions and productFlavors. Since, we need only one app we will use default
as flavorDismension. But we need 4 productFlavors local, dev, qa, and prod hence we add four blocks with some values. We’re overriding app_name
and adding applicationIdSuffix and versionNameSuffix for each flavor. This will help us differentiate between apps and generate different buildVariants. Helps with testing on the same device or simulator.
flavorDimensions "default"
productFlavors {
local {
resValue "string", "app_name", "MyApp Debug"
applicationIdSuffix '.debug'
versionNameSuffix "-DEBUG"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
dev {
resValue "string", "app_name", "MyApp Dev"
applicationIdSuffix '.dev'
versionNameSuffix "-DEV"+versionBuild
minSdkVersion rootProject.ext.minSdkVersion
applicationId "com.reactnativeproject.stage"
targetSdkVersion rootProject.ext.targetSdkVersion
}
qa {
resValue "string", "app_name", "MyApp Qa"
applicationIdSuffix '.qa'
versionNameSuffix "-QA"+versionBuild
minSdkVersion rootProject.ext.minSdkVersion
applicationId "com.reactnativeproject.stage"
targetSdkVersion rootProject.ext.targetSdkVersion
}
prod {
resValue "string", "app_name", "MyApp"
minSdkVersion rootProject.ext.minSdkVersion
applicationId "com.reactnativeproject.prod"
targetSdkVersion rootProject.ext.targetSdkVersion
}
}
Step 4: Setup environments
- Create your environment files like .env.local, .env.dev, .env.prod files and add the keys.
- Update app/build.gradle file with the following code at the beginning of the file.
project.ext.envConfigFiles = [
localdebug:".env.local",
localrelease :".env.local",
devdebug: ".env.dev",
devrelease: ".env.dev",
qadebug:'.env.qa',
qarelease:'.env.qa',
proddebug:'.env.prod',
prodrelease:'.env.prod',
]
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
def versionBuild = project.hasProperty('BUILD_NUMBER') ? "+" + project.findProperty('BUILD_NUMBER').toString() : "";
Notice how we have different combinations for buildTypes and productFlavors like productFlavorbuildTypes. So, every productFlavor has two buildTypes debug and release.
Important, thing here is to match the buildVaraint name with the correct .env file.
Now, we can access our variables inside the react components
import Config from 'react-native-config';
function HomeScreen({navigation}) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Home screen</Text>
<Text>
_**ENVIROMENT:{Config.ENVIROMENT} ::: APP_ID:{Config.APP_ID}**_
</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Stack_Details')}
/>
</View>
);
We can also access them inside the build.gradle file ex.
versionCode project.env.get("VERSION_CODE").toInteger()
To access env variables inside android .java/.kt files
URL url = new URL(BuildConfig.API_URL);
and inside iOS files
//import header
#import "ReactNativeConfig.h"
// then read individual key like:
NSString *apiUrl = [ReactNativeConfig envFor:@"API_URL"];
Final Step: Update Package.json with build commands.
Now, we just need to update scripts to build and run different build variants of our app.
"scripts": {
"android:local": "react-native run-android --mode=localDebug",
"android:dev":"react-native run-android --mode=devDebug",
"release": "cd android && .\\gradlew assembleProdRelease",
"release-aab": "cd android && .\\gradlew bundleProdRelease",
"release-dev": "cd android && .\\gradlew assembleDevRelease",
"release-qa": "cd android && .\\gradlew assembleQaRelease",
"cleanDeps": "rm -rf node_modules && npm i",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
scripts are modified to run on windows terminal.*
We can run the app locally with npm run android:local
, we can generate .apk
for prod, dev, qa with npm run release
, npm run release-dev
, npm run release-qa
respectively.
And, we can generate .aab
file for playstore submission with npm run release-aab
.
Hey, I’m Neeraj Mukta I expertise in crafting resilient apps tailored for startups. I’ve been crafting web and mobile apps for 8+ years now. If you would like to chat or need assistance with technical problems, feel free to reach out on X, Linkedin.
Top comments (0)