Environment Variables: What They Are, Usage and Configuration in Mobile Projects

Author: IT Sectr Published: 2026-05-31 Reading time: 8 min

Environment variables are dynamic values passed to an application at startup to configure behavior without changing code. They allow separating development, testing and production configurations. According to Twelve-Factor App, 2025, configuration should be stored in environment variables, not in code. Environment variables ensure secure management of API keys, backend URL and feature flags.

Key Takeaways

  • Environment variables separate application configuration from source code for different runtime environments
  • .env files store variables in KEY=VALUE format and are excluded from the repository via .gitignore
  • iOS uses xcconfig and Build Settings to pass variables at compile time
  • Android uses BuildConfig and gradle.properties to generate configuration fields
  • Security: keys and tokens should be loaded via CI/CD, not stored in code or repository

What Are Environment Variables

Environment variables are a key-value pair accessible to the application process through the operating system API. They are passed to the process when it is created and exist only during its runtime. Unlike configuration parameters embedded in source code, environment variables do not require recompilation to change values. This is a fundamental principle of the Twelve-Factor App, which ensures a clear separation between code and configuration.

In mobile development, environment variables solve the problem of different configurations for environments: the developer uses a local server, the tester uses staging, and users use production. Instead of storing three backend URLs in code with conditional if-else statements, the developer passes one URL through an environment variable at build time. This simplifies the code and eliminates the risk of accidentally using the production server in a test environment.

The main advantage is security: sensitive data does not end up in the code repository. API keys, Firebase secrets, backend access tokens and certificates are loaded via CI/CD directly into the build environment. If an attacker gains access to the code repository, they will not find secrets there, as they are stored in protected storages of the CI system and are passed only at the binary file build stage.

Why Environment Variables Are Needed in Mobile Development

Mobile projects have at least three environments: development, staging and production. Each environment requires its own set of configurations: server URL, package name, signing scheme and push notification certificates. Without environment variables, the developer has to manually change configuration before each build, which leads to errors: a forgotten production key in a test build can cause notifications to be sent to real users or consumption of paid API.

Environment Separation

Environment variables allow switching backend without changing code: just change the value in the API_BASE_URL variable. Feature flags are managed through variables like FEATURE_CHAT_ENABLED=true, allowing new features to be enabled in staging without affecting production. Each environment has its own .env file that is loaded at build time.

dart
class AppConfig {
  static final String apiBaseUrl =
    const String.fromEnvironment('API_BASE_URL',
      defaultValue: 'http://localhost:8080');
}

Key Security

Hardcoded keys are a common vulnerability in mobile applications. An attacker decompiles APK or IPA using tools like jadx or Hopper and extracts secrets from the binary file. Even obfuscation does not protect string literals — they are easily found in code after decompilation. Environment variables solve this problem by passing keys at build time via CI/CD, where they are masked in logs.

kotlin
object Config {
    val apiKey: String =
        System.getenv("API_KEY") ?: throw
            IllegalStateException("API_KEY not set")
}

CI/CD Integration

Environment variables integrate with build pipelines: GitHub Actions, GitLab CI, Bitrise and CircleCI support secret variables that are not displayed in logs. At build time, CI substitutes the appropriate values depending on the branch or tag: for the develop branch, staging is used; for the v* tag, production is used. This automates the process and eliminates the human factor, ensuring that each build receives the correct set of configuration.

.env Files and Management Libraries

The .env file is a standard way to store environment variables in KEY=VALUE format. It is not included in the repository; instead, .env.example is added with a template of all variables and empty values. Each developer creates their own .env file with local settings without affecting other team members' configurations. Separate files are used for different environments: .env.dev, .env.stage, .env.prod.

bash
# .env.example — template for developers
API_BASE_URL=http://localhost:8080
FEATURE_CHAT_ENABLED=true
SENTRY_DSN=

For mobile projects, there are specialized libraries for working with .env files:

  • flutter_dotenv (Flutter) — loads variables from .env at runtime via dotenv.load()
  • BuildConfig (Android) — generates typed fields from build.gradle values
  • xcconfig (iOS) — connects configuration files to different Xcode build schemes
  • react-native-config (React Native) — variable management through .env files

Branching settings in CI/CD allow substituting different .env files: .env.dev for test servers, .env.stage for pre-release and .env.prod for publishing to app stores. Files with secrets are loaded from a secure vault (Vault, AWS Secrets Manager) and are not stored in the repository. This ensures that even if the version control system is compromised, secrets remain protected.

Environment Variables in iOS Projects

The iOS ecosystem uses xcconfig files to manage variables at the build level. They are attached to Xcode schemes and allow overriding values for Debug and Release configurations. xcconfig files support inheritance: you can create a base file with common settings and specific files for each environment.

Configuring xcconfig Files

xcconfig files store variables in KEY = VALUE format and are attached to a build scheme in Xcode through Configuration settings. Variables from xcconfig are available in Info.plist via the $(VARIABLE_NAME) syntax, allowing different bundle identifiers and app names for different schemes. For quick environment identification, the Dev or Staging suffix is added to the app name.

bash
# Config/Dev.xcconfig — development configuration
API_BASE_URL = http://localhost:3000
BUNDLE_ID_SUFFIX = .dev
APP_DISPLAY_NAME = MyApp Dev

Swift Code for Reading Variables

For runtime access to variables in iOS, the Configuration.swift file is used, which reads values from Info.plist via Bundle.main.object(forInfoDictionaryKey:). This approach guarantees that variables are defined at build time and are available to the application immediately after launch. Values are read once during module initialization and cached for quick access throughout the application life cycle.

swift
enum AppEnvironment {
    static var apiBaseURL: URL {
        guard let urlString = Bundle.main
            .object(forInfoDictionaryKey: "API_BASE_URL"),
              let url = URL(string: urlString as! String)
        else { fatalError("API_BASE_URL is not configured") }
        return url
    }

    static var isChatEnabled: Bool {
        Bundle.main.object(
            forInfoDictionaryKey: "FEATURE_CHAT_ENABLED"
        ) as? Bool ?? false
    }
}

Environment Variables in Android Projects

Android supports environment variables through BuildConfig — an automatically generated class whose fields are defined in the build.gradle file of the module. BuildConfig is created at compile time for each flavor and build type separately. This allows having different values for debug and release without using conditional operators in code, improving performance and security.

Configuring BuildConfig Fields

BuildConfig fields are set via buildConfigField in defaultConfig or in specific buildTypes. A separate buildType or productFlavor is created for each environment. This ensures strict configuration isolation: debug uses a local server, release uses production. BuildConfig fields are statically typed, which eliminates errors when accessing them in code.

groovy
// build.gradle (Module: app)
android {
    defaultConfig {
        buildConfigField "String", "API_BASE_URL",
            "\"http://localhost:8080\""
    }
    buildTypes {
        debug {
            buildConfigField "String", "API_BASE_URL",
                "\"http://dev.api.itsectr.com\""
        }
        release {
            buildConfigField "String", "API_BASE_URL",
                "\"https://api.itsectr.com\""
        }
    }
}

gradle.properties for Shared Values

The gradle.properties file in the project root stores global Gradle variables. They are available in all modules via the $variableName syntax and are used for specifying dependency versions, build flags and API keys. Unlike BuildConfig, gradle.properties works only at the Gradle configuration stage, not at application runtime. Therefore, passwords and API keys specified in gradle.properties are not visible in decompiled code, as they are only used to generate BuildConfig at compile time.

groovy
# gradle.properties
SENTRY_DSN=https://key@sentry.io/project
MAPS_API_KEY=AIzaSy...

For secure transfer of secrets in Android projects, it is recommended to use local.properties (excluded from VCS) or load values from CI/CD variables in build.gradle via System.getenv(). This ensures that keys do not end up in the repository. When publishing to Google Play Console, make sure that all debug keys are replaced with production versions through different buildTypes or productFlavors with corresponding BuildConfig values.

Frequently Asked Questions

Can I use environment variables in Flutter?

Yes, Flutter supports environment variables through the flutter_dotenv package for runtime access or through native channels for platform variables. Dart also has the String.fromEnvironment constructor for passing values at compile time via --dart-define, which is the preferred method for Flutter projects.

What is the difference between BuildConfig and gradle.properties?

BuildConfig is a Java class with typed fields generated at compile time for each buildType and flavor. gradle.properties is a text file with key-value pairs accessible to all Gradle modules at the build configuration stage. BuildConfig works at application runtime, gradle.properties — only in Gradle scripts.

How to prevent .env file leakage into the repository?

Add .env to your repository's .gitignore file. Only commit .env.example with empty values and a description of each variable to the repository. For CI/CD, use encrypted secrets in GitHub Actions, GitLab CI or Bitrise settings, which are masked in logs and unavailable for reading after build completion.

How to pass environment variables via CI/CD?

Most CI systems support secret environment variables. In GitHub Actions these are Secrets, in GitLab CI — CI/CD Variables, in Bitrise — Secrets. At build time, they are passed to the build script via process.env or System.getenv(). Secret variables are not displayed in build logs and are unavailable in repository forks.

What are feature flags through environment variables?

Feature flags are boolean variables that control enabling or disabling functionality without recompiling code. Example: FEATURE_NEW_PAYMENT=true enables a new payment system in staging for testing. In production, the same flag is set to false until the backend is fully deployed. This allows safely rolling out changes incrementally and rolling them back if there are issues.

Summary

  • Environment variables separate configuration from source code for different development environments
  • .env files with .env.example template — the standard for managing variables in teams with environment separation
  • iOS xcconfig connects configuration files to Xcode schemes with inheritance support and Info.plist integration
  • Android BuildConfig generates typed fields from build.gradle for each buildType separately
  • CI/CD secrets pass sensitive data at build time without storing them in the repository
  • Feature flags through variables allow enabling functionality in a specific environment without recompilation
  • Security: keys are encrypted in CI and do not end up in the decompilable binary file of the application

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also