Setting up Environment Variables for Mobile App Development
Environment variables in mobile development work differently than on the web. Mobile applications have no runtime access to a server's process.env. Everything the app needs to know about its environment must be compiled into the binary at build time. This fundamentally changes the approach: instead of .env files on a server, we use build-time injection via CI and platform-specific native mechanisms.
Android: BuildConfig and manifestPlaceholders
The most straightforward way on Android is using buildConfigField in build.gradle.kts. After compilation, AGP generates a BuildConfig class with the required constants:
buildTypes {
debug {
buildConfigField("String", "API_KEY", "\"${System.getenv("API_KEY_DEV") ?: "fallback-dev-key"}\"")
buildConfigField("String", "SENTRY_DSN", "\"${System.getenv("SENTRY_DSN_DEV") ?: ""}\"")
}
release {
buildConfigField("String", "API_KEY", "\"${System.getenv("API_KEY_PROD") ?: ""}\"")
buildConfigField("String", "SENTRY_DSN", "\"${System.getenv("SENTRY_DSN_PROD") ?: ""}\"")
}
}
System.getenv() reads CI environment variables during Gradle configuration. In Kotlin code, access them via: BuildConfig.API_KEY.
For AndroidManifest.xml (for example, Google Maps API Key), use manifestPlaceholders:
defaultConfig {
manifestPlaceholders["MAPS_API_KEY"] = System.getenv("MAPS_API_KEY") ?: ""
}
In AndroidManifest.xml: android:value="${MAPS_API_KEY}".
iOS: xcconfig and Info.plist
On iOS, variables are passed through xcconfig files or directly via xcodebuild arguments:
xcodebuild -scheme MyApp \
-configuration Release \
SENTRY_DSN="$SENTRY_DSN_PROD" \
API_KEY="$API_KEY_PROD" \
archive ...
Add a key SENTRY_DSN with value $(SENTRY_DSN) to Info.plist, then access in code:
let sentryDSN = Bundle.main.object(forInfoDictionaryKey: "SENTRY_DSN") as? String ?? ""
React Native: react-native-config
The react-native-config package allows using .env files that compile into native code:
# .env.staging
API_URL=https://api-staging.myapp.com
STRIPE_KEY=pk_test_...
# .env.production
API_URL=https://api.myapp.com
STRIPE_KEY=pk_live_...
In code: import Config from 'react-native-config'; Config.API_URL. For CI, use the envfile plugin or copy the needed .env before building: cp .env.staging .env && npx react-native run-android.
Important: .env.* files with real keys must not be committed to the repository. Only .env.example with a template goes in the repo.
Flutter: --dart-define-from-file
# config/staging.json
{
"API_URL": "https://api-staging.myapp.com",
"SENTRY_DSN": "https://..."
}
flutter build apk \
--dart-define-from-file=config/staging.json \
--flavor staging
In Dart: const apiUrl = String.fromEnvironment('API_URL');
Secrets in CI
Real API keys are never stored in the repository. Use GitHub Secrets for GitHub Actions, CI/CD Variables (Masked) in GitLab CI, or Secrets in Bitrise. Variables are injected into the agent runtime, where Gradle/xcodebuild read them via System.getenv().
Process
Audit current hardcoded values → create environment-specific configuration structure → set up build-time injection → move secrets to CI variables → document for the team.
Timeline: 1–3 days. Cost is calculated individually.







