DEV Community

Sergey Shpadyrev
Sergey Shpadyrev

Posted on

How to manage environments in React Native with my open-source tool Variabler

In the last five years I have worked for four companies. In three of them I faced issues with managing environment variables for React Native apps.

Here's a brief overview of the projects:

  • In the first company I worked on 3 different apps. Each of them had 2 environments: staging and production.
  • In the second one I worked on about 10 branded apps that were based on the same white labeled codebase. All of them had 2 environments: staging and production.
  • In the third one I worked on only one app. But this app had 4 different environments: staging, staging-beta, production and production-candidate.

In all of these cases I got the following limitations:

  • Branded apps should have different bundle ids
  • Sometimes branded apps should have different version numbers
  • Apps for different environments should have different bundle ids. It's required to be able to install both staging and production apps on the same iPhone device
  • Both branded apps and environments require different settings files for services like AppCenter, Branch.io, Sentry, etc...

Developers who worked on the projects before me used one of the classic approaches:

  1. Using .env files and libraries like react-native-dotenv
  2. Using Android flavors and iOS build targets for branded apps and environments
  3. Having a lot of copies of the same files like build.gradle, Info.plist, sentry.settings, branch.json, for different environments

None of these approaches worked well:

  1. Env files approach doesn't work with bundle ids and version numbers in build.gradle for Android and Info.plist for iOS. Moreover, it doesn't work with settings files like sentry.settings or branch.json. It can only pass environment variables to JavaScript code
  2. Flavors and build targets approach makes it hard to add new brands and to manage the existing ones. Moreover, it doesn't work well with services settings files. And it doesn't allow to pass variables to JavaScript code
  3. File copies approach makes codebase look messy. And if you need to add something or to change something in one of the settings files, you need to go through all of the copies.

In all of the projects I solved the problems of managing environments and brands with the following approach:

  1. I created template files for all the service settings files, for all the JavaScript constants files and for the build.gradle and Info.plist files. In these template files I put variable keys wrapped by @ signs. E.g.: @VARIABLE_KEY@
  2. I created a config file describing all the variable values for all the environments and brands.
  3. I created a config file describing where to copy these template files
  4. I put all the copy destinations to .gitignore to avoid making git changes every time I set another environment.
  5. I wrote a script that takes these configs and templates, fills variables into templates for needed environment and copies these filled templates to their destination paths.

It always worked great!

So I decided to release my tool to open-source. I called it Variabler.
Let's see how easily you can manage environments using it.

Case 1: Two environments

Let's say, we need to have two environments for our app: staging and production.

Step 1: We create template files:

api.js:

const baseURL = '@API_URL@'
export const get = url => fetch('GET', `${baseUrl}/${url}`)
Enter fullscreen mode Exit fullscreen mode

build.gradle:

...
applicationId "@BUNDLE_ID@"
versionName "@VERSION@"
...
Enter fullscreen mode Exit fullscreen mode

Step 2: We create variables config:

{
  "common": {
    "VERSION": "1.2.3"
  },
  "env": {
    "staging": {
      "API_URL": "https://staging.example.com",
      "BUNDLE_ID": "com.example.app.staging"
    },
    "production": {
      "API_URL": "https://production.example.com",
      "BUNDLE_ID": "com.example.app"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: We add template paths config:

[
  { "from": "api.js", "to": "./src/api.js" },
  { "from": "build.gradle", "to": "./android/app/build.gradle" }
]
Enter fullscreen mode Exit fullscreen mode

Step 4: We add file destination paths to .gitignore:

/android/app/build.gradle
/src/api.js
Enter fullscreen mode Exit fullscreen mode

So that's it!

Now we can easily set environment using the Variabler:

variabler set env:staging
Enter fullscreen mode Exit fullscreen mode

Result: this command execution created two files.

android/app/build.gradle:

...
applicationId "com.example.app.staging"
versionName "1.2.3"
...
Enter fullscreen mode Exit fullscreen mode

src/api.js:

const baseURL = 'https://staging.example.com'

export const get = url => fetch('GET', `${baseUrl}/${url}`)
Enter fullscreen mode Exit fullscreen mode

Case 2: Two environments and two brands

Let's say, we need:

  • to have two environments for our app: staging and production
  • to build the app for two different brands: cola and pepsi

Step 1: We create build.gradle file template:

...
applicationId "@BUNDLE_ID@@BUNDLE_EXTENSION@"
versionName "@VERSION@"
...
Enter fullscreen mode Exit fullscreen mode

Step 2: We create variables config:

{
  "brand": {
    "cola": {
      "BUNDLE_ID": "com.example.cola"
    },
    "pepsi": {
      "BUNDLE_ID": "com.example.pepsi"
    }
  },
  "common": {
    "VERSION": "1.2.3"
  },
  "env": {
    "staging": {
      "BUNDLE_EXTENSION": ".staging"
    },
    "production": {
      "BUNDLE_EXTENSION": ""
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: We add template paths config:

[{ "from": "build.gradle", "to": "./android/app/build.gradle" }]
Enter fullscreen mode Exit fullscreen mode

Step 4: We add file destination paths to .gitignore:

/android/app/build.gradle
Enter fullscreen mode Exit fullscreen mode

That's it.

Now we can set variables:

variabler set brand:pepsi env:staging
Enter fullscreen mode Exit fullscreen mode

Result: we gonna see the following code in android/app/build.gradle:

...
applicationId "com.example.pepsi.staging"
versionName "1.2.3"
...
Enter fullscreen mode Exit fullscreen mode

Getting started with Variabler

You don't need to make all the described steps manually.
Variabler can do it for you.

To start using Variabler you need to install it and init it in your project repository:

npm i -g variabler
cd ./your-react-native-project
variabler init
Enter fullscreen mode Exit fullscreen mode

Then you can start make files to be managed by Variabler:

variabler add ./android/app/build.gradle
Enter fullscreen mode Exit fullscreen mode

After you add variable keys to templates and variable values to variables.json you can simply run something like:

variabler set env:staging
Enter fullscreen mode Exit fullscreen mode

That's simple!

For getting better understanding of how to install and start using Variabler visit the GitHub repository.

Not only React Native but React and Node.js

Even if Variabler was created for React Native, indeed there're no reasons why it can't be used for React and Node.js applications or any other type of JavaScript projects. It's absolutely platform independent.

Afterwords

I hope Variabler will serve a good service for some developers.
Feel free to report bugs, create issues on GitHub and send me your pull requests.

Discussion (0)