Skip to content

Developers often create different builds (development, staging, production) with varied configurations. This guide provides examples for efficient build differentiation, optimizing workflow, and enhancing app quality.

License

Notifications You must be signed in to change notification settings

Pubuditha-Jayasekara/react_native_multiple_environment_setup

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🚀 React Native Multiple Environment configuration (Android & IOS)

Many times when developing an application, we developers need to create different builds with different configurations. Facilitating the maintenance and testing process. Usually 3 different builds are created: development, staging and production.

This repository serves as a comprehensive guide, accompanied by illustrative examples, demonstrating how to efficiently achieve this build differentiation. By following this guide, developers can optimize their workflow and ensure smoother transitions between development stages while enhancing the overall quality of their applications.

🛠️ Pre-Requisites for React Native Multiple Environment Configuration

Before you begin setting up multiple environment configurations in your React Native project, ensure you have the following pre-requisites in place:

1. React Native Development Environment:

Make sure you have a fully functional React Native development environment set up on your machine, including Node.js, npm (Node Package Manager), and the React Native CLI.

2. React Native Project:

Have an existing React Native project ready. If you don't have one yet, create a new project using the React Native CLI.

📱 Android Configuration:

  1. Install react-native-config package.

    yarn add react-native-config
    

You can find the package on GitHub here. The react-native-config package allows you to efficiently manage environment-specific configurations in your React Native project, making it simpler to handle different builds with varying settings.

  1. Create separate “.env” files for desired environments in the project root directory. In mine I have '.env' for default configurations,‘.env.dev’ for development configuration, ‘.env.prod’ for production configuration and ‘.env.qa’ for qa testing configurations.
  • EX path for ‘.env.dev’ : projectName/.env.dev
Screenshot 2023-07-31 at 11 27 10 AM
  1. Update each .env file to contain following details.

    APP_VERSION="1.0"
    BUILD_NUMBER=9
    
    Screenshot 2023-07-31 at 11 29 00 AM
  2. Go to the app level ‘build.gradle’ file in "/your project directory/android/app/build.gradle"

    Screenshot 2023-07-31 at 10 14 43 AM
  3. Add the following environment configurations at the very top of the ‘build.gradle’ file but below the apply plugin: "com.android.application" , apply plugin: "com.facebook.react" and import com.android.build.OutputFile lines.

          //MARK: to handle each environment .env file
          project.ext.envConfigFiles = [
              devDebug: ".env.dev",
              devRelease: ".env.dev",
              qaRelease: ".env.qa",
              qaDebug: ".env.qa",
              prodRelease: ".env.prod",
              prodDebug: ".env.prod"
          ]
    
    Screenshot 2023-07-31 at 10 26 23 AM
  4. Next add the following line below the ‘project.ext.envConfigFiles‘ configuration.

       apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
    
    Screenshot 2023-07-31 at 10 29 17 AM
  5. Add the product flavors inside the ‘android{} ‘ below the ‘buildTypes{}’ in ‘build.gradle’ file.

         //MARK: to handle each environment flavor
         productFlavors {
             dev {
                 dimension "default"
                 minSdkVersion rootProject.ext.minSdkVersion
                 applicationId 'com.daily.dailydev'
                 targetSdkVersion rootProject.ext.targetSdkVersion
                 resValue "string", "build_config_package", "com.daily"
                 resValue "string", "app_name" , "Daily Dev"
                 versionCode project.env.get("BUILD_NUMBER").toInteger()
                 versionName project.env.get("APP_VERSION")
             }
             qa {
                 dimension "default"
                 minSdkVersion rootProject.ext.minSdkVersion
                 applicationId 'com.daily.dailyqa'
                 targetSdkVersion rootProject.ext.targetSdkVersion
                 resValue "string", "build_config_package", "com.daily" 
                 resValue "string", "app_name" , "Daily Qa"
                 versionCode project.env.get("BUILD_NUMBER").toInteger()
                 versionName project.env.get("APP_VERSION")
             }
             prod {
                 dimension "default"
                 minSdkVersion rootProject.ext.minSdkVersion
                 applicationId 'com.daily.dailyprod'
                 targetSdkVersion rootProject.ext.targetSdkVersion
                 resValue "string", "build_config_package", "com.daily" 
                 resValue "string", "app_name" , "Daily Prod"
                 versionCode project.env.get("BUILD_NUMBER").toInteger()
                 versionName project.env.get("APP_VERSION")
             }
         }
    
    Screenshot 2023-07-31 at 10 39 22 AM

🪚 Let's break down the code:

  • productFlavors: This is a section that defines multiple product flavors, each representing a different environment configuration.

  • dev: This flavor represents the development environment. It specifies the following configurations:

    • dimension "default": This sets the flavor dimension to "default."
    • minSdkVersion rootProject.ext.minSdkVersion: Sets the minimum SDK version for this flavor, which is retrieved from a variable defined in the root project's gradle.properties.
    • applicationId 'com.daily.dailydev': Sets the application ID (package name) for the development flavor.
    • targetSdkVersion rootProject.ext.targetSdkVersion: Sets the target SDK version for this flavor, which is retrieved from a variable defined in the root project's gradle.properties.
    • resValue "string", "build_config_package", "com.daily": This adds a resource value with the name "build_config_package" and the value "com.daily" to the flavor's resources.
    • resValue "string", "app_name" , "Daily Dev": This adds a resource value with the name "app_name" and the value "Daily Dev" to the flavor's resources.
    • versionCode project.env.get("BUILD_NUMBER").toInteger(): Sets the version code for the development flavor, which is retrieved from an environment variable named "BUILD_NUMBER."
    • versionName project.env.get("APP_VERSION"): Sets the version name for the development flavor, which is retrieved from an environment variable named "APP_VERSION."
  • qa: This flavor represents the QA testing environment. It has similar configurations to the dev flavor but with different values for the application ID and app name to distinguish it as the QA build.

  • prod: This flavor represents the production environment. It again shares similar configurations with the previous two flavors but with a unique application ID and app name to identify it as the production build.

  1. Once you update the app name in the ‘app.gradle’ file you need to comment or remove the app name from the res file located in "your project directory/android/app/src/main/res/values/strings.xml"

    Screenshot 2023-07-31 at 10 46 45 AM

🛎️ Important

  1. You have to provide a valid flavorDimensions once you add the productFlavors. To achive this you can place the following code inside buildTypes{} before the debug{} and release{} code segments.

      //MARK: set flavour dimension 
      flavorDimensions "default"
    
    Screenshot 2023-07-31 at 11 23 09 AM

📄 Manage Multiple Google-Services.Json configurations:

If you have multiple google-services.json files: you need to add following code after adding product flavors code in ‘build.gradle’ file as follows. This also needs to be inside the ‘android{}’ section.

     //MARK: to handle google service.json per each product flavor
     applicationVariants.all { variant ->
         variant.productFlavors.each { flavor ->
             // Get the flavor-specific `google-services.json` file path
             def googleServicesJsonPath = "src/${flavor.name}/res/raw/${flavor.name}.json"

             variant.registerGeneratedResFolders(files(googleServicesJsonPath))

             // Apply the `google-services` plugin with the flavor-specific file
             variant.buildConfigField "String", "GOOGLE_SERVICES_JSON_FILE", "\"${googleServicesJsonPath}\""
             variant.resValue "string", "google_services_json_file", googleServicesJsonPath
         }
     }
Screenshot 2023-07-31 at 11 37 28 AM

🛎️ Important

Once you update multiple google-service.json handling part in app.gradle, you need to update the android folder structure to match each json file for each environment like follows.

  1. Open your Android project folder.
  2. Navigate to the ‘src’ folder inside the project/android/app.
  3. Inside the ‘src’ folder, create three new folders: qa, dev, and prod.
  4. Inside each of the newly created qa, dev, and prod folders, create a folder named ‘res’.
  5. Now, within each res folder (inside qa, dev, and prod), create a folder named ‘raw’. This raw folder will be used to store the google-service.json files for each environment.
  6. Finally, you need to add the appropriate google-service.json file to each raw folder. Make sure you rename each file to match its respective environment.

For example, the google-service.json file inside the dev folder should be renamed to dev.json. Similarly, rename the files in the other folders also.

Screenshot 2023-07-31 at 11 00 02 AM

Let's Run Each Flavour

  1. Update the ‘package.json’ file with following code to reflect the changes and run each environment using yarn/npm.
       "android:dev": "react-native run-android --variant=devDebug --appId com.daily.dailydev",
       "android:dev-release": "react-native run-android --variant=devRelease --appId com.daily.dailydev",
       "android:qa": "react-native run-android --variant=qaDebug --appId com.daily.dailyqa",
       "android:qa-release": "react-native run-android --variant=qaRelease --appId com.daily.dailyqa",
       "android:prod": "react-native run-android --variant=prodDebug --appId com.daily.dailyprod",
       "android:prod-release": "react-native run-android --variant=prodRelease --appId com.daily.dailyprod"
Screenshot 2023-07-31 at 11 06 42 AM
  1. In order to run each environment you can use the command ‘yarn android:dev’ etc.

Dev Debug:

yarn android:dev

Dev Release:

Yarn android:dev-release

Qa Debug:

yarn android:qa

Qa Release:

Yarn android:qa-release

Prod Debug:

yarn android:prod

Prod Release:

Yarn android:prod-release

Once you run each falvour there should be three different apps installed like below.

Screenshot 2023-07-31 at 11 47 05 AM

📱 iOS Configuration:

📦 react-native-config Package Configuration:

  1. Create Config.xconfig file in xcode project root.

    • Right click on project name -> new file
    Screenshot 2023-07-31 at 2 55 40 PM
    • Search for word "config" -> select configurations settings file
    Screenshot 2023-07-31 at 2 55 53 PM
    • Set target -> select create
    Screenshot 2023-07-31 at 2 56 04 PM
    • Add the following code inside the 'Config.xconfig' file

      #include? "tmp.xcconfig"
      
      Screenshot 2023-07-31 at 2 55 07 PM
      • Update your podfile by adding following code line.
      pod 'react-native-config', :path => '../node_modules/react-native-config'
      
      Screenshot 2023-07-31 at 5 26 02 PM

💻 XCode Configuration:

  1. First we need to add new build configurations.

    *Select project name -> goto info tab -> select configurations.

    Screenshot 2023-07-31 at 12 51 48 PM
  2. Click on the plus sign below in ‘Configurations’ and select ‘Duplicate Debug Configuration’ for debug builds and ‘Duplicate Release Configuration’ for release builds and rename it to match your desired schemas.

    Screenshot 2023-07-31 at 12 57 14 PM
Screenshot 2023-07-31 at 12 59 40 PM
  1. Next we need to create required schemes (dev, prod and qa).

    a. Goto product -> scheme -> edit scheme.

Screenshot 2023-07-31 at 1 08 04 PM

b. The menu will be selected to your default scheme and you need to click on the duplicate scheme at the very bottom. This will duplicate your current scheme and you can configure this scheme by changing its name to desired (dev, prod and qa) also we need to update the Run and Archive configuration as described below.

Screenshot 2023-07-31 at 1 10 12 PM

c. Just make sure that schema that you duplicate is “shared” and has the right build configuration reference in Run and Archive .

  • Goto‘Run’ -> info tab -> build configuration. From this drop down please select the suitable debug build configuration where you add in the first step. See the image below.

    Screenshot 2023-07-31 at 1 16 49 PM
  • Goto ‘Archive’ -> build configuration. From this drop down please select the suitable release build configuration where you add in the first step. See the image below.

Screenshot 2023-07-31 at 1 23 58 PM
  • Add the pre build scripts to communicate with .env file and properly work with react-native-config package we added

    Screenshot 2023-07-31 at 4 35 09 PM
  • Each pre build scripts need to contain following scripts

    "${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"
    

    Make sure to cahnge '.env.prod' to match your env file. For ex: .env.dev or .env.qa etc.

    cp "${PROJECT_DIR}/../.env.prod" "${PROJECT_DIR}/../.env"
    

🛎️ Important

  • It is required to set a valid target to the 'Provide build settings from' drop down menue. You can simply add your target name there.

    Screenshot 2023-07-31 at 4 34 49 PM

    d. You have to repeat step 2 and 3 until you create all the schemas you need (dev, prod and qa).

  1. Update the bundle identifiers for each schema.

    • Select project name -> TARGETS -> App Name -> Build Settings -> search for word ‘identifier’.

    • This will give you something called ‘Packaging’ inside that you can find the product bundle identifiers. Select each scheme name and give the appropriate bundle identifier for each.

      Screenshot 2023-07-31 at 2 23 31 PM
  2. Update the bundle name for each schema.

    • Select project name -> TARGETS -> App Name -> Build Settings -> search for word 'product name'.

    • This will give you something called ‘Packaging’ inside that you can find the product names. Select each scheme name and give the appropriate product name for each. This will be the app display name once you install in a device.

      Screenshot 2023-07-31 at 5 15 42 PM
  3. You have to double check if the given display names are configured correctly.

    • Select project name -> TARGETS ->App Name -> General -> Display name -> click on arrow or three dots.

    • This will open ‘Display Name’ popup and you can add or verify the given display names for each schema.

      Screenshot 2023-07-31 at 2 06 51 PM
  4. You can double check the Bundle Identifiers also in the same way.

    Screenshot 2023-07-31 at 2 26 08 PM
  5. Finally Update the info.plist for grabbing suitable values from each configuration as follows.

    Bundle display name -> $(PRODUCT_NAME)

    Executable file -> $(EXECUTABLE_NAME)

    Bundle identifier -> $(PRODUCT_BUNDLE_IDENTIFIER)

    Bundle name -> $(PRODUCT_NAME)

    Bundle version string (short) -> $(APP_VERSION)

    Bundle version -> $(BUILD_NUMBER)

    Screenshot 2023-07-31 at 2 31 01 PM
  6. Run each build config by selecting schemes from the dropdown menu as follows.

    Screenshot 2023-07-31 at 2 33 36 PM

Once you run each falvour there should be three different apps installed like below.

Screenshot 2023-07-31 at 2 33 36 PM

📄 Manage Multiple GoogleService-Info.plist configurations for IOS APP:

  1. Create a folder called 'FireBase' in the project root and add each GoogleService-Info.plist file for each product flavour. (make sure to rename them according to their flavour names as showen in the below image)
Screenshot 2023-09-13 at 6 34 23 AM
  1. Also create an empty file called 'GoogleService-Info.plist' in the project root at the same level of 'Firebase' directory you created according to the above image. This helps you to prevent app crashing at very first running.

  2. You have to add following run script for each schema you have created before (make sure to rename each GoogleService-Info.plist file anme to match each flavours).

       cp "${PROJECT_DIR}/Firebase/GoogleService-Info-dev.plist" "${PROJECT_DIR}/GoogleService-Info.plist"
    
Screenshot 2023-09-13 at 6 43 30 AM

About

Developers often create different builds (development, staging, production) with varied configurations. This guide provides examples for efficient build differentiation, optimizing workflow, and enhancing app quality.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published