I want to be able to access a variable in my metro.config.js.
On iOS this can be achieved by adding a User Defined variable in the Build Settings tab. This is accessible with process.env.MY_VAR. The value of MY_VAR can be changed depending on which target is being built.
How can I achieve the same for Android using product flavours?
Here is my current setup
flavorDimensions "apps"
productFlavors {
free {
dimension "apps"
}
paid {
dimension "apps"
}
}
Execution Steps
When running iOS, I can see from the logs that the project gets built before the Packager starts
info Found Xcode workspace "foo.xcworkspace"
info Building (using "xcodebuild -workspace foo.xcworkspace -configuration Debug -scheme foo -destination id=CE408502-CD8C-4467-AE3D-295081CAF132 -derivedDataPath build/foo") <-- Build
▸ Running script '[CP-User] Config codegen'
▸ Compiling ReactNativeConfig.m
▸ Building library libreact-native-config.a
▸ Running script 'Upload Debug Symbols to Sentry'
▸ Running script 'Start Packager <-- Metro starts here
However, the packager starts before my gradle build task executes
info Starting JS server... <-- Metro starts here
info Installing the app...
> Configure project :app
Reading env from: .env
> Task :app:installFreeDebug <-- Build
Add the following codes to your build.gradle file.
buildTypes.each {
it.buildConfigField 'String', 'KEY_STRING', '"Default_Value"'
it.buildConfigField 'int', 'KEY_INT', '0'
}
applicationVariants.all { variant ->
if (variant.productFlavors[0].ext.has("key_1")) {
buildConfigField('String', 'KEY_STRING', variant.productFlavors.get(0).ext.key_1)
}
if (variant.productFlavors[0].ext.has("key_2")) {
buildConfigField('int', 'KEY_INT', variant.productFlavors.get(0).ext.key_2)
}
}
and then add ext.key_1 = '"desired string value"' and ext.key_2 = 'desired integer value' to flavors like this
flavorDimensions "apps"
productFlavors {
free {
dimension "apps"
ext.key_1 = '"free_value"'
ext.key_2 = '0'
}
paid {
dimension "apps"
ext.key_1 = '"paid_value"'
ext.ket_2 = '100'
}
}
Now you will access to the values in your app by calling BuildConfig.KEY_STRING and BuildConfig.KEY_INT.
If you don't set value for key_1 in a flavor, then the default value will be "Default_Value".
Update
I'm ashamed to say my above answer is wrong because in this way you can access the BuildConfig in JS scope but as you mentioned in your question you need to access the configuration in Metro Bundler and metor.config.js. I added the console.log(process.env); at the beginning of metro.config.js file and figured out process.env in Metro Bundler is my OS (in my case Windows 10) environment variables. So it means if you add desired variables as OS environment variable, then you will access them in metro.config.js through process.env. I tried to add environment variables through build.gradle but it wasn't a good way because:
1. I couldn't find a way to add the environment variable in Gradle
2. If I could do step 1, then as you mentioned the Metro starts before android Build, so the configuration will be ready late and Metro can't access them.
So, I decided to solve this issue in another way. First, I changed the build.gradle to log the required variables during Build process.
flavorDimensions "apps"
productFlavors {
free {
dimension "apps"
// Add any configuration you need for free flavor
buildConfigField('String', 'KEY_STRING', '"free_value"')
}
paid {
dimension "apps"
// Add any configuration you need for paid flavor
buildConfigField('String', 'KEY_STRING', '"paid_value1"')
buildConfigField('int', 'KEY_INT', '100')
}
}
applicationVariants.all { variant ->
// Read all flavors configuration
variant.productFlavors.each { flavor ->
flavor.buildConfigFields.each { key, value ->
// Set configuration to variant to be accessible through BuildConfig
variant.buildConfigField(value.type, value.name, value.value)
// Log all configurations in output
println "[" + variant.name + "]---" + value.name + "=" + value.value
}
}
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
Then I wrote a bat file to run {variant}PreBundle task (For example freeDebugPreBundle) in Gradle. Then it will read the output and extract required variables and then set them as environment variables and finally call react-native run-android --variant {variant} command.
run-android.bat
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: Reading input arguments to extract desired variant
FOR %%a IN (%*) do (
set "arg=%%a"
if "!found!"=="true" (
set "variant=%%a"
GOTO:EndOfLoop
)
if "!arg!"=="--variant" set found=true
)
:EndOfLoop
:: User must provide variant because when you have flavors, then `debug` varinat does not exist anymore
if "!variant!"=="" (
echo You must provide --variant
GOTO:EOF
)
:: Creating a pttern to extract required variables
set search=[!variant!]---
:: Creating gradle task name based on input variant
set firstCharUpper=%variant:~0,1%
CALL :UpCase firstCharUpper
set taskName=build!firstCharUpper!%variant:~1%PreBundle
echo Running !taskName!
:: Creating an empty bat file to store environment variables and call later
echo. > custome_env.bat
:: Running gradle task and extracting required variables from output log
cd android
FOR /F "tokens=* USEBACKQ" %%F IN (`gradlew app:!taskName!`) DO (
SET "line=%%F"
SET "newLine=!line:%search%=!"
if not "!line!"=="!newLine!" (
echo !line!
set "keyValue=!line:%search%=!"
:: Adding environment variabel to custome_env.bat file
echo set !keyValue!>>../custome_env.bat
)
)
ENDLOCAL
:: Running custome_env.bat file to set required variables as environment variable
call custome_env.bat
:: Calling react-native command with all input parameters (%*)
call react-native run-android %*
GOTO:EOF
@echo on
:UpCase
:: Subroutine to convert a variable VALUE to all UPPER CASE.
:: The argument for this subroutine is the variable NAME.
FOR %%i IN ("a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I" "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R" "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z") DO CALL SET "%1=%%%1:%%~i%%"
GOTO:EOF
For running the application you should run this command:
run-android.bat --variant freeDebug
You can use other react-native options as well but you must provide --variant because when you have flavors, default variant debug does not exist.
PS: I don't know how is your release proceseduar, so I'm not sure this answer is compatible with your release procedure or not. if I know it maybe I can find a solution for it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With