“Wait… You’re Using .env Files in Flutter for Secrets?” Let’s Talk Before It’s Too Late

Table of contents
- 😱 The Mistake: Trusting .env Files in Production
- ✅ The Solution: Use --dart-define to Pass Secrets Securely
- 🤔 What If You Have Many Secrets? Use --dart-define-from-file
- 📦 Use Different Configs for Different Environments
- 🚫 What Not to Do
- 🔐 Bonus: Obfuscation + Code Shrinking = Stronger Defense
- 🧠 TL; DR – When to Use What
- 🧪 Real App Example
- 🙌 Final Thoughts

That’s literally what I asked a friend recently when I cracked open their Flutter app’s .apk
.
Spoiler: the .env
file was right there, readable like a TODO comment.
And honestly? It’s not just them. I’ve seen this mistake across multiple teams. If you’re using flutter_dotenv
without understanding what it does in production, you’re probably exposing secrets too.
So, let’s sit down, dev-to-dev, and go through:
Why this happens
How to properly pass secrets
When to use
--dart-define
vs.--dart-define-from-file
And how to secure your Flutter apps like a pro
😱 The Mistake: Trusting .env
Files in Production
Here’s how it usually goes:
You install flutter_dotenv
, create a .env
file like this:
API_URL=https://myapi.com
API_KEY=super_secret_key
Then in your code:
import 'package:flutter_dotenv/flutter_dotenv.dart';
final apiUrl = dotenv.env['API_URL'];
final apiKey = dotenv.env['API_KEY'];
Looks clean, right?
🚨 But here’s the catch:
flutter:
assets:
- .env
This tells Flutter to bundle .env
as a plain asset. That means in your final .apk
(which is just a ZIP file), the .env
file is just sitting there under /assets/
, ready to be opened by anyone who downloads your app.
Don’t believe me? Try it:
unzip build/app/outputs/flutter-apk/app-release.apk
cat assets/.env
Yup. Your secrets are not secreting anymore.
✅ The Solution: Use --dart-define
to Pass Secrets Securely
Flutter gives you a better way: --dart-define
.
Instead of loading secrets from a file, you inject them at build time, and retrieve them from Dart using String.fromEnvironment
.
🛠 How to Use It
flutter build apk \
--release \
--obfuscate \
--split-debug-info=./debug_info \
--dart-define=API_URL=https://myapi.com \
--dart-define=API_KEY=super_secret_key
And in your Dart code:
const apiUrl = String.fromEnvironment('API_URL');
const apiKey = String.fromEnvironment('API_KEY');
These values are compiled into the app binary — not bundled as assets — making them much harder to extract.
🤔 What If You Have Many Secrets? Use --dart-define-from-file
Now imagine this: you have 5, 10, maybe 15 different keys and toggles — API URLs, feature flags, auth tokens, etc.
Typing all of them into a CLI command gets ugly fast. That’s where --dart-define-from-file
comes in.
🔄 Instead of this:
flutter build apk \
--dart-define=API_URL=https://... \
--dart-define=API_KEY=... \
--dart-define=FEATURE_X=true \
--dart-define=CLIENT_ID=...
✅ Do this:
- Create a config file:
config.prod.json
{
"API_URL": "https://myapi.com",
"API_KEY": "super_secret_key",
"FEATURE_X": "true",
"CLIENT_ID": "xyz-123"
}
- Build your app using:
flutter build apk \
--release \
--obfuscate \
--split-debug-info=./debug_info \
--dart-define-from-file=config.prod.json
- Access values like before:
const apiUrl = String.fromEnvironment('API_URL');
const apiKey = String.fromEnvironment('API_KEY');
📦 Use Different Configs for Different Environments
Want to switch between environments?
Make multiple config files:
config.dev.json
config.staging.json
config.prod.json
Then:
flutter build apk --dart-define-from-file=config.dev.json
Easy to manage. Clean. Secure.
🚫 What Not to Do
Here are some real-world mistakes I’ve seen:
❌ Bundling .env
in assets
❌ Checking config.prod.json
with secrets into Git
❌ Hardcoding secrets in Dart
❌ Skipping code obfuscation
🔐 Bonus: Obfuscation + Code Shrinking = Stronger Defense
Reverse engineering is always a risk, but you can make it much harder:
✅ Enable R8
In android/gradle.properties
:
android.enableR8=true
✅ Obfuscate Your Code
flutter build apk \
--release \
--obfuscate \
--split-debug-info=./debug_info \
--dart-define-from-file=config.prod.json
✅ Add ProGuard Rules
Edit android/app/proguard-rules.pro
to hide sensitive class names or prevent reflection.
🧠 TL; DR – When to Use What
Method | Use Case | Secure for Production? | Scalable? |
.env + flutter_dotenv | Dev only | ❌ No | 🔸 Limited |
--dart-define | Small set of secrets | ✅ Yes | 🔸 Medium |
--dart-define-from-file | Many secrets or different env’s | ✅ Yes | ✅ Excellent |
🧪 Real App Example
Let’s say your app needs:
API_URL
API_KEY
AUTH_DOMAIN
FIREBASE_ID
FEATURE_X
toggle
Create config.prod.json
:
{
"API_URL": "https://api.myapp.com",
"API_KEY": "prod-key",
"AUTH_DOMAIN": "auth.myapp.com",
"FIREBASE_ID": "abcd1234",
"FEATURE_X": "true"
}
Then build your app:
flutter build apk \
--release \
--obfuscate \
--split-debug-info=./debug_info \
--dart-define-from-file=config.prod.json
You now have a secure, production-ready build — without secrets exposed in assets.
🙌 Final Thoughts
If you’ve made it this far, here’s what I want you to remember:
.env
is not safe for production — don’t ship it.Use
--dart-define
or--dart-define-from-file
instead.Keep secrets out of your frontend when possible.
Obfuscate. Shrink. Encrypt. Always.
👉 Don’t wait for a security audit or a breach to start caring about this.
Your users trust your app — protect that trust.
Subscribe to my newsletter
Read articles from Md. Al - Amin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Md. Al - Amin
Md. Al - Amin
Experienced Android Developer with a demonstrated history of working for the IT industry. Skilled in JAVA, Dart, Flutter, and Teamwork. Strong Application Development professional with a Bachelor's degree focused in Computer Science & Engineering from Daffodil International University-DIU.