Shared Packages
How to manage Flutter environment variables per flavor
Free Reference for shared_app_config: every variable, option and package it generates. Command: archipelago generate shared_app_config
Generate the shared config package with envied, keep variables per flavor, read them through AppEnv, and add a new variable.
What you'll learn
- Generating the shared config package with
envied - Keeping environment variables per flavor
- Reading values through the public
AppEnvwrapper - Adding a variable and switching flavors
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
New projects already include this brick. The monorepo generator also writes the .env.development, .env.staging and .env.production files with placeholder values.
Step 1: Generate the Shared App Config
archipelago generate shared_app_configThe prompts are:
- appName: your app name, for example
MyApp - analyticsVendors: the analytics vendors you use.
appsflyer,mixpanelandmoengageadd key getters.firebaseadds nothing. - featureFlagVendor:
flagradds a base URL getter. The other values add nothing. - isForMonorepo: leave this as
true
Or use a config file:
{
"appName": "MyApp",
"analyticsVendors": [],
"featureFlagVendor": "none",
"isForMonorepo": true
}archipelago generate shared_app_config --config config.jsonVendor bricks read AppEnv.<key> directly. They fail to compile with undefined_getter if you did not select their vendor here.
Step 2: Understand the Generated Structure
shared/app_config/
├── .env.example # Placeholder template, committed
├── .gitignore # Ignores .env and env.g.dart
├── build.yaml
├── lib/
│ ├── app_config.dart # Barrel: AppEnv and Flavor
│ └── src/
│ ├── env/
│ │ ├── env.dart # Internal @Envied class
│ │ └── app_env.dart # Public AppEnv wrapper
│ └── flavor.dart # FlavorStatus and Flavor
└── pubspec.yamlenv.g.dart is generated by build_runner. It holds the obfuscated values and is not committed.
Step 3: Set the Variables per Flavor
The values live in .env.<flavor> files next to .env.example. monorepo_toolkit env init creates them from the example if they are missing.
# .env.development
API_BASE_URL=https://api-dev.myapp.com
APP_NAME=MyApp Dev
AES_KEY=replace-with-a-dev-key
DATABASE_URL=replace-meUse other values in .env.production. Do not commit real secrets. Fill the production file from your CI secret store.
Step 4: Switch Flavors and Generate
env switch copies the matching file to .env. Then build_runner reads only that flavor:
dart run monorepo_toolkit env switch --flavor development
dart run build_runner build --delete-conflicting-outputsStep 5: Read Values in Code
Application code uses AppEnv. It never touches the internal Env class:
import 'package:app_config/app_config.dart';
final baseUrl = AppEnv.apiBaseUrl;
final appName = AppEnv.appName;Vendor getters such as AppEnv.appsflyerDevKey, AppEnv.mixpanelToken, AppEnv.moengageAppId and AppEnv.flagrBaseUrl exist only when you selected that vendor.
Step 6: Add a Variable
Run the helper:
dart run monorepo_toolkit env add MY_VAROr do it by hand:
- Add an
@EnviedField()toenv.dartand a getter toapp_env.dart. - Add the key to
.env.exampleand every.env.<flavor>file. - Run
env switchfor your current flavor. - Run
build_runner.
Step 7: Use the Flavor
Set the flavor once in main() before first use:
Flavor.status = FlavorStatus.development;
if (Flavor.isProduction) {
// production-only behavior
}Flavor.current throws a StateError if the status is not set.
Why Envied Over dart-define
| Approach | Downside |
|---|---|
--dart-define | Values show in the process list and are not obfuscated |
envied | Values are compiled in and obfuscated |
Envied generates obfuscated Dart code, so secrets are not plain strings in the binary. Obfuscation slows extraction. It does not make a secret safe. Never ship a server secret in an app.
Common Customizations
| Customization | Where to Change |
|---|---|
| Add env vars | .env.* files, env.dart and app_env.dart |
| Change flavors | flavor.dart and monorepo_toolkit flavor |
| CI secrets | Write the .env.<flavor> file from the CI secret store |
Next Steps
- Set up Shared Dependencies for centralized package management
- Configure build_prepare for flavor-based builds