Multiple environments in Flutter (w VSCode & Firebase)

Erlend
3 min readApr 17, 2023

NOTE: I was recently made aware that the Flutter team are removing --dart-define-from-file having access to native platform specific code, see this issue. Some points of this article are still viable though, like copying a Firebase config as a pre-build step and accessing configuration entries in pure Dart code.

If you are building a Flutter app of decent size, involving a backend and multiple users, you might want to support multiple environments like development and production.

You’d want to use the development environment to test the app without interfering with the current users and when ready, push these changes to the production environment.

Using multiple flavors for Android and iOS can be quite cumbersome, involving creating multiple build configurations or flavors for each platform.

You’d perhaps also like to support Firebase and multiple configuration files based on the current platform and environment.

Dart define from file to the rescue

You can do this quite easily now in Flutter without multiple build targets / flavors / schemes, by just using —dart-define-from-file, a Visual Studio Code (VSCode) configuration and a few bash files.

I’d used —dart-definefor a while when i discovered — dart-define-from-from-file through this article. This simplifies passing configurations a lot by using a simple file for all API keys and secrets. It also makes parsing and using the values natively much easier.

Using —dart-define-from-filewe can pass a separate JSON file for each build environment and add them as separate entries in `launch.json`.

I’d like to keep the configuration files in a separate `config` folder which would look like this:

MyApp/config/config-dev.json
MyApp/config/config-prod.json

These variables are now available in Flutter using `String.fromEnvironment`.

You can access them natively on iOS via `Generated.xcconfig` and on Android in `build.gradle` via `project.getProperty`.

Firebase configuration

If you are using Firebase and you are using the `firebase cli` to generate multiple configurations as Dart files (`main_dev.dart` and `main_prod.dart`), in some cases you still need to add the Firebase configuration files (`GoogleService-Info.plist` and `google-services.json`). And you want to add the correct config file based on the current environment.

I ended up downloading all the configuration files and renaming them based on the environment:

GoogleService-Info-dev.plist
GoogleService-Info-prod.plist
google-services-dev.json
google-services-prod.json

Then in `launch.json` i added a `preLaunchTask`, which calls a bash script that copies the Firebase config file to the right folder.

Here is a `launch.json` example:

“configurations”: [
{
“name”: “MyApp dev”,
“request”: “launch”,
“type”: “dart”,
“flutterMode”: “debug”,
“program”: “lib/main_dev.dart”,
“preLaunchTask”: “copy-config-dev-task”,
“args”: [
“ — dart-define-from-file=config/config-dev.json”
],
},
{
“name”: “MyApp prod”,
“request”: “launch”,
“type”: “dart”,
“flutterMode”: “debug”,
“program”: “lib/main_prod.dart”,
“preLaunchTask”: “copy-config-prod-task”,
“args”: [
“ — dart-define-from-file=config/config-prod.json”
],
},

The `preLaunchTask` is defined in a separate file in the `.vscode` folder called `tasks.json`. It looks like this:

{
“version”: “2.0.0”,
“tasks”: [
{
“label”: “copy-config-dev-task”,
“type”: “shell”,
“command”: “./copy-config-dev.sh”
},
{
“label”: “copy-config-prod-task”,
“type”: “shell”,
“command”: “./copy-config-prod.sh”
}
]
}

Each task calls a separate bash script which copies the configuration to the correct folder. E.g. here is the content of `copy-config-dev.sh`:

cp config/GoogleService-Info-dev.plist ios/Runner/GoogleService-Info.plist
cp config/google-services-dev.json android/app/google-services.json

Simple build scripts

You can now add two simple build scripts for Android and iOS to build a production app.

For iOS, `build-ios.sh`:

./copy-config-prod.sh
flutter build ipa \
— target=lib/main_prod.dart \
— dart-define-from-file=config/config-prod.json \

For Android, `build-android.sh`:

./copy-config-prod.sh
flutter build appbundle \
--target=lib/main_prod.dart \
--dart-define-from-file=config/config-prod.json \

That’s it — a simple build configuration for Flutter and VSCode to support multiple environment.

Thanks to the author of the ` — dart-define-from-file` article. Check out his articles if you want to better understand how to access the dart define variables.

--

--