Background Info
We are not using eas or expo go, instead we are using our own provisioning profiles and a combination of app.json and babel.config.js.
We want to transition from dev environment to qa/staging environments. We are using azure pipelines to run expo’s prebuild command for ios and android builds. For example: npx expo prebuild --platform ios --clean --npm
I was successful in setting up the dev environment to run with the following command… npx expo run dev-android
(which points to the following script in package.json "dev-android": "cross-env NODE_ENV=development npx expo run:android --port 8082"
The following successfully prints out in the development environment…
The Structure
The Code…
App.tsx
import React, { useEffect } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import '@app/utils/IgnoreWarnings';
import Toast from 'react-native-toast-message';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { GlobalContextProvider, useLoadingStore } from '@app/stores';
import { ToastInit } from '@app/components';
import { NavigationConductor } from '@app/navigation';
import { toastFailedProps } from '@app/utils';
import { RANDOM_ENV_DEV, RANDOM_ENV_QA } from '@env';
import type { LoadingStore } from '@app/types';
// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();
export default function App() {
// variables
const { loadInitialAppData } = useLoadingStore((store: LoadingStore) => store);
// setup
useEffect(() => {
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('RANDOM_ENV_DEV:', RANDOM_ENV_DEV);
console.log('RANDOM_ENV_QA:', RANDOM_ENV_QA);
const loadApp = async () => {
try {
await loadInitialAppData();
} catch (error: any) {
Toast.show(toastFailedProps(error.message));
} finally {
await SplashScreen.hideAsync();
}
};
loadApp();
}, [loadInitialAppData]);
// render
return (
<GlobalContextProvider>
<SafeAreaProvider>
<NavigationConductor />
<ToastInit />
</SafeAreaProvider>
</GlobalContextProvider>
);
}
.env
RANDOM_ENV_DEV=HelloWorldDev
RANDOM_ENV_QA=HelloWorldQa
babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['module:metro-react-native-babel-preset', 'babel-preset-expo'],
plugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
extensions: ['.js', '.jsx', '.ts', '.tsx', '.android.js', '.android.tsx', '.ios.js', '.ios.tsx'],
alias: {
'@': './',
'@app': './app'
}
}
],
[
'module:react-native-dotenv',
{
envName: 'APP_ENV',
moduleName: '@env',
path: '.env',
blocklist: null,
allowlist: null,
blacklist: null, // DEPRECATED
whitelist: null, // DEPRECATED
safe: false,
allowUndefined: true,
verbose: false
}
]
]
};
};
app.json (config file)
{
"expo": {
"name": "PROJECTNAME",
"slug": "PROJECTSLUG",
"version": "1.0.0",
"scheme": "msauth",
"orientation": "portrait",
"icon": "./assets/images/qa-icon-1024.png",
"userInterfaceStyle": "light",
"backgroundColor": "#001689",
"splash": {
"image": "./assets/images/qa-splash.png",
"resizeMode": "contain",
"backgroundColor": "#001689"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.example.qa",
"buildNumber": "1.0.0"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/qa-icon-1024.png",
"backgroundColor": "#001689"
},
"package": "com.example.qa",
"versionCode": 1
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
[
"expo-build-properties",
{
"android": {
"minSdkVersion": 23
}
}
],
[
"expo-image-picker",
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos",
"cameraPermissions": "Allow $(PRODUCT_NAME) to access your camera"
}
]
]
}
}
package.json
{
"name": "Project-Name",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"ts:check": "tsc",
"dev-android": "cross-env NODE_ENV=development npx expo run:android --port 8082",
"dev-ios": "cross-env NODE_ENV=development npx expo run:ios --port 8082",
"android": "expo run:android",
"ios": "expo run:ios"
},
"dependencies": {
"@expo/vector-icons": "^13.0.0",
"@react-native-async-storage/async-storage": "1.18.2",
"@react-native-community/netinfo": "9.3.10",
"@react-navigation/native": "^6.1.9",
"@react-navigation/stack": "^6.3.20",
"axios": "^1.6.2",
"core-js": "^3.35.0",
"expo": "~49.0.15",
"expo-auth-session": "~5.0.2",
"expo-build-properties": "^0.8.3",
"expo-constants": "~14.4.2",
"expo-file-system": "~15.4.5",
"expo-image-manipulator": "~11.3.0",
"expo-image-picker": "~14.3.2",
"expo-secure-store": "~12.3.1",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0",
"expo-system-ui": "~2.4.0",
"expo-web-browser": "~12.3.2",
"formik": "^2.4.5",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"react": "18.2.0",
"react-native": "0.72.10",
"react-native-device-info": "^11.1.0",
"react-native-element-dropdown": "^2.10.1",
"react-native-gesture-handler": "~2.12.0",
"react-native-get-random-values": "~1.9.0",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"react-native-toast-message": "^2.1.9",
"react-query": "^3.39.3",
"scandit-react-native-datacapture-barcode": "^6.21.3",
"scandit-react-native-datacapture-core": "^6.21.3",
"uri-scheme": "^1.1.0",
"uuid": "^9.0.1",
"yup": "^1.3.2",
"zustand": "^4.4.7"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/lodash": "^4.14.202",
"@types/react": "~18.2.14",
"@types/uuid": "^9.0.7",
"cross-env": "^7.0.3",
"react-native-dotenv": "^3.4.10",
"typescript": "^5.1.3"
},
"private": true
}
Env.ts
declare module '@env' {
export const RANDOM_ENV_DEV: string;
export const RANDOM_ENV_QA: string;
}
declare var process: {
env: {
NODE_ENV: string;
};
};
Results
This is working in the development environment where process.env.NODE_ENV
reads as development
.
The Question
Now how do we make it where when we use expo prebuild
command to package a build that would include process.env.NODE_ENV
will read as uat
or qa
?
2
Answers
The only solution I could find for this was still a bit hacky, but at least it is in one place.
babel.config.js
change to...
Just avoid doing any rebase between the branches and when you upload the changes per branch in the right order then future merges will not overwrite this value.
It's still not an ideal solution, but at least the value is changed in only one location.
Since you already have react-native-dotenv you can create multiple .env files to cater your needs on UAT or QA
In your package.json file