- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
That’s literally what I asked a friend recently when I cracked open their Flutter app’s .apk.“Bro, did you just ship your API key in plain text inside the 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
Here’s how it usually goes:
You install flutter_dotenv, create a .env file like this:
API_URL=
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.
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= \
--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=...
- Create a config file:
config.prod.json
{
"API_URL": "",
"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:
? Bonus: Obfuscation + Code Shrinking = Stronger Defense
Reverse engineering is always a risk, but you can make it much harder:
In android/gradle.properties:
android.enableR8=true
flutter build apk \
--release \
--obfuscate \
--split-debug-info=./debug_info \
--dart-define-from-file=config.prod.json
Edit android/app/proguard-rules.pro to hide sensitive class names or prevent reflection.
? TL; DR – When to Use What
- .env + flutter_dotenv — Use Case (Dev only) — Secure for Production? (
No) — Scalable? (? Limited) - --dart-define — Use Case (Small set of secrets) — Secure for Production? (
) — Scalable? (? Medium) - --dart-define-from-file — Use Case (Many secrets or different env's) — Secure for Production? (
) — Scalable? (
Excellent)
Let’s say your app needs:
- API_URL
- API_KEY
- AUTH_DOMAIN
- FIREBASE_ID
- FEATURE_X toggle
Create config.prod.json:
{
"API_URL": "",
"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.