How to manage environments in React Native with my open-source tool Variabler
Tags: reactnative, react, opensource, environement
Published: 2022-06-22 21:54:43

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](https://github.com/sergeyshpadyrev/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}`)
```

`build.gradle`:

```
...
applicationId "@BUNDLE_ID@"
versionName "@VERSION@"
...
```

_Step 2_: We create variables config:

```json
{
  "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"
    }
  }
}
```

_Step 3_: We add template paths config:

```json
[
  { "from": "api.js", "to": "./src/api.js" },
  { "from": "build.gradle", "to": "./android/app/build.gradle" }
]
```

_Step 4_: We add file destination paths to `.gitignore`:

```
/android/app/build.gradle
/src/api.js
```

_So that's it!_ 

Now we can easily set environment using the Variabler:

```sh
variabler set env:staging
```

_Result_: this command execution created two files.

`android/app/build.gradle`:

```
...
applicationId "com.example.app.staging"
versionName "1.2.3"
...
```

`src/api.js`:

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

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

**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@"
...
```

_Step 2_: We create variables config:

```json
{
  "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": ""
    }
  }
}
```

_Step 3_: We add template paths config:

```json
[{ "from": "build.gradle", "to": "./android/app/build.gradle" }]
```

_Step 4_: We add file destination paths to `.gitignore`:

```
/android/app/build.gradle
```

_That's it._

Now we can set variables:

```sh
variabler set brand:pepsi env:staging
```

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

```
...
applicationId "com.example.pepsi.staging"
versionName "1.2.3"
...
```

**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:

```sh
npm i -g variabler
cd ./your-react-native-project
variabler init
```

Then you can start make files to be managed by Variabler:
```sh
variabler add ./android/app/build.gradle
```

After you add variable keys to templates and variable values to `variables.json` you can simply run something like:
```sh
variabler set env:staging
```

_That's simple!_

For getting better understanding of how to install and start using Variabler visit the [GitHub repository](https://github.com/sergeyshpadyrev/variabler).

**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.