12

So I'm happy with the concept of environment variables as explained in this article and others https://www.freecodecamp.org/news/how-to-gracefully-use-environment-variables-in-a-react-native-app/

Great, I've got my SOMETHING="something" stored so I can just use env.SOMETHING or whatever

The part I'm a little lost on is where you keep the live variables?

I would rather not do a solution like this as it seems you are still keeping your keys quite public and that you are just choosing based on the environment with if statements

Manage environment with expo react native

For example with an Express App deployment we have, we specify

let endPointURL = env.endPointURL

and then we keep a versoin of that variable locally and when it sits with AWS it is overridden by AWS servers as explained here

I was just wondering does something like that exist for Android and iOS builds (on the respective stores) or through Expo?

Thanks all

Ash Hogarth
  • 497
  • 2
  • 6
  • 18

4 Answers4

16

Honestly I think the way they go about it is a little silly. There may be a better way to go about than this, but I think I followed their documentation suggestions.

https://docs.expo.io/versions/latest/distribution/release-channels/#using-release-channels-for-environment-variable-configuration

They have a code snippet suggesting you create a function to look at the release configuration itself.

I interpreted it that you might do something like the code below and store your environment variables in a variables.js file and pull in your environment variables as such.

import Constants from 'expo-constants';

export const prodUrl = "https://someapp.herokuapp.com";

const ENV = {
  dev: {
    apiUrl: "http://localhost:3000"
  },
  staging: {
    apiUrl: prodUrl
  },
  prod: {
    apiUrl: prodUrl
  }
};

function getEnvVars(env = "") {
  if (env === null || env === undefined || env === "") return ENV.dev;
  if (env.indexOf("dev") !== -1) return ENV.dev;
  if (env.indexOf("staging") !== -1) return ENV.staging;
  if (env.indexOf("prod") !== -1) return ENV.prod;
}

export default getEnvVars(Constants.manifest.releaseChannel);

Edit:

Now that Expo supports config file as app.config.js or app.config.ts, we can use the dotenv. Check this: https://docs.expo.io/guides/environment-variables/#using-a-dotenv-file

alfx
  • 174
  • 2
  • 14
Caleb Davenport
  • 613
  • 5
  • 18
  • 2
    After a (long!) deep dive into how this is done, I'll select your answer @Caleb. What a shame there isn't a simpler mechanism for this! I've ended up keeping two folders - one for live and one for dev with a different folder for env variables that I just git push and pull to to keep the source code up to day. Very silly put it works! :'D – Ash Hogarth Oct 24 '19 at 10:26
  • I was rather hoping I would be proven wrong. Oh well. Most of the answers are "just get off expo" – Caleb Davenport Oct 24 '19 at 21:02
  • 1
    An alternative solution for @AshHogarth might be to keep both configuration folders in one repo, and have one file which switches between them when needed. Add this file to `.gitignore` and generate it each time you build. E.g. `config.now.js` simply contains `module.exports = require('./config.staging/index.js')` – joeytwiddle Nov 08 '19 at 08:41
  • 1
    I am trying that and I can say it does not work well, the expo web can get the new env, but the android and ios app are stuck with one I can't even change. – Dimitri Kopriwa Dec 28 '19 at 19:00
6

A simpler approach would be to export the env object instead of the function:

import Constants from 'expo-constants';
import { Platform } from "react-native";

const localhost =
 Platform.OS === "ios" ? "localhost:8080" : "10.0.2.2:8080";


const ENV = {
    dev: {
      apiUrl: localhost,
      amplitudeApiKey: null,
    },
    staging: {
      apiUrl: "[your.staging.api.here]",
      amplitudeApiKey: "[Enter your key here]",
      // Add other keys you want here
    },
    prod: {
      apiUrl: "[your.production.api.here]",
      amplitudeApiKey: "[Enter your key here]",
      // Add other keys you want here
    }
};

const getEnvVars = (env = Constants.manifest.releaseChannel) => {
  if (env === null || env === undefined || env === "" || env.indexOf("dev") !== -1) return ENV.dev;
  if (env.indexOf("staging") !== -1) return ENV.staging;
  if (env.indexOf("prod") !== -1) return ENV.prod;
}

const selectedENV = getEnvVars();

export default selectedENV;

// Import
import env from '..xxx/utility/env';
  • You're solution is the same as the accepted answer except for that you break up the method call. The accepted answer isn't returning a function. It's returning the results of the function just like your answer. – VtoCorleone Feb 20 '21 at 15:18
1

Get it in your ios-generated file, based on .env file:

  1. In .env, write down GOOGLE_MAPS_API=abcde...
  2. yarn add react-native-config
  3. cd ios
  4. pod install
  5. In your Objective-C-compiled code, for example, AppDelegate.m:
#import "ReactNativeConfig.h"
NSString *mapsApiKey = [ReactNativeConfig envFor:@"GOOGLE_MAPS_API"];
[GMSServices provideAPIKey:mapsApiKey];

Credits to: ReactNative: Pass JS variable to AppDelegate based on https://github.com/luggit/react-native-config.

Android should work as well, but haven't tested / followed https://github.com/luggit/react-native-config. Edit: required steps for Android:

  1. <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/GOOGLE_MAPS_API"/> in AndroidManifest.xml.
  2. In settings.gradle:
include ':react-native-config'
project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android')

right after rootProject.name = 'XYZ'

  1. In build.gradle: apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" right below import com.android.build.OutputFile and implementation project(':react-native-config') right below implementation "com.facebook.react:react-native:+" // From node_modules

Regarding "normal" usage in .ts, .tsx, .js files I'm declaring variables in .env based on https://github.com/goatandsheep/react-native-dotenv by declaring "module:react-native-dotenv" in babel.config.js in plugins, and it works like a charm like so:

import { ACCESS_TOKEN } from "@env";
...
headers: {
  Authorization: `Bearer ${ACCESS_TOKEN}`,
  Accept: "application/json",
},

Edit: important the eas build ignores .gitignore-declared variable, so if your .env is in .gitignore the production bundle won't have it included.

Daniel Danielecki
  • 8,508
  • 6
  • 68
  • 94
  • 1
    I've been breaking my head over this last part "eas build ignores .gitignore-declared variable". – Gijsriet Mar 24 '23 at 13:48
  • @Gijsriet, I did as well :) – Daniel Danielecki Mar 25 '23 at 12:49
  • Now, there's a warning if your build fails with `eas`: * Android build failed: "google-services.json" is missing, make sure that the file exists. Remember that EAS Build only uploads the files tracked by git. Use EAS secrets to provide EAS Build with the file. Learn more* It redirects to https://docs.expo.dev/build-reference/variables/#how-to-upload-a-secret-file-and – Daniel Danielecki Apr 04 '23 at 03:46
1

Surprised there weren't any answers that involved storing environment variables in a .env file with Expo.

I prefer storing my environment variables in a .env file because I don't want to commit certain variables to version control and hardwire them into my application code.

  1. Create your .env file and add your environment variables

  2. Install dotenv

npm install dotenv
  1. In your app.config.js file, load the environment variables from the .env file via dotenv:
require("dotenv").config();

export default {
    // config...
}
  1. Expose the environment variables to the Expo runtime in the config in app.config.js:
require("dotenv").config();

export default {
    // rest of config...
    extra: {
        ENV_VAR: process.env.ENV_VAR
    }
}

Now you can access your environment variables through the following:

import Constants from "expo-constants";
const ENV_VAR = Constants.expoConfig.extra.ENV_VAR

OPTIONAL: TypeScript

To make using environment variables in our code a little nicer, lets create a typed helper utility to access the environment variables:

import Constants from "expo-constants";

export interface Env {
  ENV_VAR: string;
}

/**
 * Environment variables exposed through `app.config.js`
 * An environment variable not there? Make sure it is explicitly defined in `app.config.js`
 */
export const env = Constants.expoConfig?.extra as Env;

Then you can simply access your environment variables from the env object:

const ENV_VAR = env.ENV_VAR

OPTIONAL: Throw an error if an environment variable is not set

This can be handy to prevent your app from running if an environment variable required for your app to properly function is not set.

In your app.config.js:

// Validate all necessary environment variables are set
const checkForEnvVariable = (envVar) => {
  if (!process.env[envVar]) {
    throw Error(`${envVar} not set! Check env.md for more information`);
  }
};
[
  "ENV_VAR",
  // ...
].forEach((envVar) => checkForEnvVariable(envVar));
Armster
  • 772
  • 1
  • 9
  • 25