Stop Typing Flutter Commands: Launch Configurations for QA & Prod
Part 3 of the Flutter Environment Configuration & Flavors Series
In the previous article, we used --dart-define to separate QA and Production environments.
It solved a real problem.
Our application could now point to different APIs, use different Firebase projects, and behave differently based on the selected environment.
But after using this setup for a few weeks, another problem starts to appear.
Not a technical problem.
A workflow problem.
The Problem Nobody Talks About
Imagine you're working on a bug reported by QA.
You run:
flutter run --dart-define=ENV=qa
A few hours later, you need to verify something in production.
So you run:
flutter run --dart-define=ENV=prod
The next day you continue development.
You open VS Code.
You click Run.
The app launches.
A few minutes later you're wondering:
Which environment am I actually running right now?
QA?
Production?
Something else?
You check logs.
You check API calls.
You restart the application.
Eventually you figure it out.
This is what I call command fatigue.
The commands themselves aren't difficult.
The problem is having to remember them dozens of times every week.
When Environment Configuration Becomes A Workflow Problem
Most articles about Flutter environments focus on setup.
Very few discuss daily usage.
But that's where teams spend most of their time.
The challenge isn't writing:
flutter run --dart-define=ENV=qa
The challenge is remembering to write it correctly every single time.
Over time, developers end up with:
Saved terminal snippets
Notes in Notion
Slack messages containing commands
Team onboarding documents explaining how to launch the app
The workflow starts becoming more complicated than the configuration itself.
And that's usually a sign that something can be improved.
The Feature Most Flutter Developers Ignore
VS Code already provides a solution.
Launch Configurations.
Instead of remembering commands, you define them once and give them meaningful names.
For example:
QA
Production
QA Release
Production Release
Now instead of typing commands, you simply choose the configuration you want and launch the application.
The environment becomes visible before the app even starts.
Understanding What VS Code Is Actually Doing
When you click Run in VS Code, Flutter launches the application using a configuration.
If you don't provide one, VS Code uses its default behavior.
That's fine for small projects.
But once you have multiple environments, multiple devices, or different build modes, relying on defaults quickly becomes frustrating.
Launch configurations let you make those decisions explicit.
Debug vs Release Builds
One common misconception is that environments and build modes are the same thing.
They're not.
These are build modes:
Debug
Profile
Release
These are environments:
Development
QA
Staging
Production
You can combine them however you like.
For example:
| Build Mode | Environment |
|---|---|
| Debug | QA |
| Debug | Production |
| Release | QA |
| Release | Production |
A QA build can run in Debug mode.
A Production environment can also run in Debug mode.
They're solving different problems.
Build modes affect performance and debugging capabilities.
Environments affect application behavior and configuration.
Creating Your First Launch Configuration
Create the following file inside your project:
.vscode/launch.json
Then add your configurations.
{
"version": "0.2.0",
"configurations": [
{
"name": "QA",
"request": "launch",
"type": "dart",
"args": [
"--dart-define=ENV=qa"
]
},
{
"name": "Production",
"request": "launch",
"type": "dart",
"args": [
"--dart-define=ENV=prod"
]
}
]
}
VS Code now displays both options directly in the Run & Debug panel.
No terminal commands.
No copy-pasting.
No remembering.
Just select the environment and start coding.
Creating Release Launches
Sometimes you want to verify behavior closer to production.
You can create dedicated release configurations.
{
"name": "QA Release",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"args": [
"--dart-define=ENV=qa"
]
}
Or:
{
"name": "Production Release",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"args": [
"--dart-define=ENV=prod"
]
}
Now switching between Debug and Release becomes a single click.
The Device Selection Surprise
Another source of confusion appears once multiple devices are connected.
Suppose you have:
Android Emulator
Physical Android device
iPhone Simulator
When you press Run, Flutter automatically chooses a device.
Most of the time that's fine.
Sometimes it's not the device you expected.
You end up waiting for an emulator to start while your physical device sits unused.
Or the application launches on the wrong platform entirely.
The deviceId Gotcha
Launch configurations can also solve this problem.
You can explicitly specify the target device.
{
"name": "QA Android",
"request": "launch",
"type": "dart",
"deviceId": "android",
"args": [
"--dart-define=ENV=qa"
]
}
Or:
{
"name": "QA iPhone",
"request": "launch",
"type": "dart",
"deviceId": "iphone",
"args": [
"--dart-define=ENV=qa"
]
}
The exact device IDs can be found using:
flutter devices
Once configured, Flutter launches directly on the intended device.
No manual selection required.
Platform-Specific Launch Configurations
As projects grow, many teams create dedicated configurations per platform.
For example:
QA Android
QA iOS
QA Web
Production Android
Production iOS
Production Web
This may seem excessive initially.
But when you're switching environments dozens of times per day, these small optimizations save a surprising amount of time.
More importantly, they eliminate mistakes.
A Clean launch.json Example
A practical setup might look like this:
{
"version": "0.2.0",
"configurations": [
{
"name": "QA",
"request": "launch",
"type": "dart",
"args": [
"--dart-define=ENV=qa"
]
},
{
"name": "Production",
"request": "launch",
"type": "dart",
"args": [
"--dart-define=ENV=prod"
]
},
{
"name": "QA Release",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"args": [
"--dart-define=ENV=qa"
]
}
]
}
Simple.
Easy to understand.
Easy to maintain.
Exactly what a team wants.
Why This Matters More Than It Seems
Launch configurations don't change your architecture.
They don't improve performance.
They don't reduce bundle size.
What they improve is something equally important:
developer experience.
Instead of remembering commands, developers select configurations.
Instead of checking logs, developers know which environment they're running.
Instead of repeating setup steps, developers focus on building features.
It's one of those changes that takes five minutes to implement and pays dividends every day afterward.
Final Thoughts
--dart-define solved the environment configuration problem.
Launch configurations solve the environment usage problem.
Together, they create a workflow that's easier to understand, easier to maintain, and much harder to misuse and now that running the correct build is effortless, we're ready for the next step.