DEV Community

Cover image for “Wait… You’re Using .env Files in Flutter for Secrets?” Let’s Talk Before It’s Too Late
Md. Al-Amin
Md. Al-Amin

Posted on

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

“Bro, did you just ship your API key in plain text inside the APK?”

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
Enter fullscreen mode Exit fullscreen mode

Then in your code:

import 'package:flutter_dotenv/flutter_dotenv.dart';

final apiUrl = dotenv.env['API_URL'];
final apiKey = dotenv.env['API_KEY'];
Enter fullscreen mode Exit fullscreen mode

Looks clean, right?

🚨 But here’s the catch:

flutter:
  assets:
    - .env
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And in your Dart code:

const apiUrl = String.fromEnvironment('API_URL');
const apiKey = String.fromEnvironment('API_KEY');
Enter fullscreen mode Exit fullscreen mode

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=...
Enter fullscreen mode Exit fullscreen mode

✅ Do this:

  1. Create a config file:

config.prod.json

{
  "API_URL": "https://myapi.com",
  "API_KEY": "super_secret_key",
  "FEATURE_X": "true",
  "CLIENT_ID": "xyz-123"
}
Enter fullscreen mode Exit fullscreen mode
  1. Build your app using:
flutter build apk \
  --release \
  --obfuscate \
  --split-debug-info=./debug_info \
  --dart-define-from-file=config.prod.json
Enter fullscreen mode Exit fullscreen mode
  1. Access values like before:
const apiUrl = String.fromEnvironment('API_URL');
const apiKey = String.fromEnvironment('API_KEY');
Enter fullscreen mode Exit fullscreen mode

📦 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

✅ Obfuscate Your Code

flutter build apk \
  --release \
  --obfuscate \
  --split-debug-info=./debug_info \
  --dart-define-from-file=config.prod.json
Enter fullscreen mode Exit fullscreen mode

✅ Add ProGuard Rules
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? (✅ Yes) — Scalable? (🔸 Medium)
  • --dart-define-from-file — Use Case (Many secrets or different env's) — Secure for Production? (✅ Yes) — Scalable? (✅ 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"
}
Enter fullscreen mode Exit fullscreen mode

Then build your app:

flutter build apk \
  --release \
  --obfuscate \
  --split-debug-info=./debug_info \
  --dart-define-from-file=config.prod.json
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (2)

Collapse
 
krishhandro profile image
krishh

Glad to know that, although I did not know about flutter_dotenv, I am already using dart-define in my project. Thank you for sharing.

Collapse
 
alaminkarno profile image
Md. Al-Amin

You're welcome