| Android flavors |
android/app/build.gradle |
environment flavor dimension: dev (.dev), staging (.staging), prod. Per-flavor app_name via resValue. debuggableVariants lists the flavored debug variants. |
Side-by-side installs per environment (ADR 0004) |
app.config.ts per APP_ENV, or a flavors config plugin |
| Android env values |
android/app/build.gradle |
Each flavor reads its own .env.<flavor> and adds every key as a buildConfigField + resValue (applyEnvFile). react-native-config’s dotenv.gradle is not applied. buildConfig = true; build_config_package resValue |
dotenv.gradle chooses one .env per Gradle run from the first task name, so ./gradlew assembleRelease or assembleDevDebug assembleProdRelease baked dev values into the prod app (found 2026-10-10). CI checks each build with scripts/ci/check-env.sh |
app.config.ts + expo-constants |
| Android strings |
android/app/src/main/res/values/strings.xml |
app_name removed (comes from the flavor) |
Avoids a duplicate resource |
n/a |
| Android release |
android/app/build.gradle, proguard-rules.pro |
R8 minifyEnabled + shrinkResources on; keep rule for BuildConfig; release signing from RN_UPLOAD_* properties or env vars, debug-key fallback with a warning |
Smaller release builds; no keys in git |
EAS credentials |
| Android icons |
android/app/src/<flavor>/res/mipmap-* |
Generated by pnpm icons; main has none |
Badge per environment |
icon per environment in app.config.ts |
| Splash (Android) |
MainActivity.kt, res/values/styles.xml, AndroidManifest.xml, res/drawable*/bootsplash_logo.png, res/values/colors.xml |
RNBootSplash.init(this, R.style.BootTheme) before super.onCreate; BootTheme set on the activity (generated by pnpm splash) |
Native splash until JS is ready |
expo-splash-screen |
| Splash (iOS) |
AppDelegate.swift, BootSplash.storyboard, Colors.xcassets, Images.xcassets/BootSplashLogo-*, Info.plist |
customize(_ rootView:) calls RNBootSplash.initWithStoryboard; UILaunchStoryboardName = BootSplash; template LaunchScreen.storyboard removed |
Same |
expo-splash-screen |
| iOS environments |
ios/Config/*.xcconfig, project.pbxproj, xcshareddata/xcschemes/* |
Six configurations (Debug/Release × Dev/Staging/Prod), each based on an xcconfig that includes the Pods xcconfig; schemes <App>-Dev/-Staging/-Prod; template Debug/Release and single scheme removed. Generated by scripts/ios/configure-environments.rb |
Per-environment bundle id, display name and icon (ADR 0004) |
app.config.ts per APP_ENV |
| iOS scene life cycle |
AppDelegate.swift, SceneDelegate.swift (new), Info.plist (UIApplicationSceneManifest), project.pbxproj |
The window and React Native start in a UIWindowSceneDelegate; AppDelegate only returns true. Cold-start URLs from connectionOptions are passed as launch options; runtime URLs and universal links are forwarded to RCTLinkingManager. The bootsplash hook moved here. |
Apps built with the iOS 27 SDK (Xcode 27) are killed at launch without the UIScene life cycle (“UIScene life cycle is required for apps built with this SDK”). The RN 0.87 template still uses the app-delegate window; RN’s main template has a SceneDelegate, backported here to 0.87 APIs |
Handled by Expo’s app delegate |
| iOS Swift DEBUG flag |
ios/Config/Debug-*.xcconfig, ios/Podfile |
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DEBUG on the app’s and the pods’ Debug-* configurations |
RN only sets it on a configuration named exactly Debug; without it #if DEBUG is false in Debug-Dev and the app looks for an embedded bundle instead of Metro (“No script URL provided”) |
n/a |
| iOS Info.plist |
ios/<App>/Info.plist |
CFBundleDisplayName = $(APP_DISPLAY_NAME) |
Name comes from the environment xcconfig |
name in app.config.ts |
| iOS icons |
Images.xcassets/AppIcon{,-Dev,-Staging}.appiconset |
Single-size 1024 px icon per environment, chosen by ASSETCATALOG_COMPILER_APPICON_NAME |
Badge per environment |
icon per environment |
| iOS Podfile |
ios/Podfile |
project maps the six configurations to debug/release; post_install sets ENVFILE on the react-native-config pod per configuration |
CocoaPods and react-native-config understand the custom configurations |
n/a (managed by prebuild) |