Why Environment Management Matters
Mobile apps typically need to connect to different backends depending on the context: a local server during development, a staging server for QA testing, and a production server for real users. Beyond API URLs, environments differ in analytics keys, feature flags, logging levels, and third-party service configurations.
Unlike web apps where you can change environment variables and redeploy in seconds, mobile apps are compiled binaries. Switching environments requires rebuilding the app or building environment logic into the app itself.
Common Environment Setup
Most teams use three environments:
- Development: Local or shared dev server. Verbose logging. Test data. Debug tools enabled.
- Staging: Production-like environment with test data. Used by QA and stakeholders.
- Production: Real users, real data, real payments. Minimal logging. Error tracking enabled.
Platform-Specific Approaches
iOS (Xcode Configurations)
Xcode supports build configurations (Debug, Release by default). You can add custom configurations (Staging) and map them to schemes:
- Each configuration can have different Info.plist values
- Use xcconfig files to define per-environment settings
- Schemes tie configurations to build actions (Run = Debug, Archive = Release)
Android (Build Variants)
Android's Gradle build system supports flavors and build types:
- Build types: debug, release (different signing, ProGuard settings)
- Product flavors: dev, staging, production (different API URLs, app IDs)
- Each combination produces a distinct APK/AAB
- Different applicationId per flavor allows installing multiple environments side by side
Cross-Platform Approaches
.env Files
The most common cross-platform approach. Create separate files for each environment:
- .env.development: API_URL=http://localhost:8000
- .env.staging: API_URL=https://staging-api.example.com
- .env.production: API_URL=https://api.example.com
React Native
Use react-native-config: reads .env files and exposes values as Config.API_URL. Works with both iOS and Android.
Expo
Expo supports environment variables through .env files (loaded automatically), app.config.js (reads process.env), and eas.json (per build profile). Access values with process.env.EXPO_PUBLIC_API_URL.
Flutter
Use flutter_dotenv or --dart-define flags to pass compile-time constants.
Build Profiles (EAS)
Expo's EAS Build uses eas.json to define build profiles. Each profile specifies a channel, environment variables, distribution type, and build configuration. This is the cleanest approach for Expo projects.
Feature Flags
Feature flags decouple deployment from release. Ship code to production with features hidden behind flags, then enable them remotely:
- Compile-time flags: Removed from the binary entirely. Requires a new build to change.
- Runtime flags: Checked at runtime. Can be toggled without a new build.
Remote Feature Flag Services
- Firebase Remote Config: Free, widely used, supports A/B testing
- LaunchDarkly: Enterprise-grade, real-time updates, audience targeting
- Statsig: Feature flags with built-in analytics
Implementation Pattern
- Define flags with default values (disabled by default for new features)
- On app launch, fetch the latest flag values from the remote service
- Cache flag values locally for offline access
- Check flags before rendering features or executing code paths
Secrets Management
Mobile apps have a fundamental limitation: anything bundled in the binary can be extracted. This affects how you handle secrets:
Safe to Include
- API base URLs, public API keys (with domain restrictions), analytics identifiers, feature flag defaults
Never Include
- Database credentials, server-side API secrets, payment processing keys, admin tokens
For secrets that must exist on the device, use your backend as a proxy. The mobile app authenticates with your backend, and your backend communicates with third-party services.
Best Practices
- Use separate app IDs per environment so you can install dev, staging, and production side by side
- Add a visual indicator for non-production builds so testers know which environment they are using
- Never ship debug builds to production
- Keep .env files out of version control but document required variables in a .env.example file
- Automate environment switching through CI/CD build profiles