Sidney OkineSidney Okine
All posts

Stop Hardcoding Secrets in Flutter – Use --dart-define Instead

Use --dart-define for Flutter build configuration, understand its security limits, and keep privileged secrets on a backend.

Sidney OkineSidney Okine

If you're a Flutter beginner like myself, there's a good chance you've created a constants.dart file somewhere in your project and thrown your API keys in there like this:

❌ The Problem: Hardcoding Secrets

class AppConstants {
  AppConstants._();

  static const apiKey = "abcdefghijkl";
}

Look familiar? We've all been there. It works, it's easy, and honestly—it feels harmless. But here's the catch: this approach is a security anti-pattern.

The moment you push this code to a public repository (or even a private one with multiple collaborators), your API keys are out in the wild. Anyone with access to your codebase can see them. Worse, they'll live forever in your git history even if you delete them later. 😬

So what's the solution?


✅ The Solution: Compile-Time Environment Variables

Instead of hardcoding secrets, you can inject them at compile time using Flutter's --dart-define flag—technically known as a compile-time environment variable or compile-time constant declaration.

This approach keeps configuration out of source control, but it does not make client-side values secret. Values passed with --dart-define are compiled into the app and can still be extracted. Use it for build configuration and restricted public API keys; keep privileged credentials and sensitive operations on a backend.

How It Works

When running or building your Flutter app, you can pass environment variables like this:

# For release mode
flutter run --release --dart-define=API_KEY=abcdefghijkl

# For debug mode
flutter run --dart-define=API_KEY=abcdefghijkl

# For iOS builds (IPA)
flutter build ipa --dart-define=API_KEY=abcdefghijkl

# For Android builds (APK/App Bundle)
flutter build apk --dart-define=API_KEY=abcdefghijkl
flutter build appbundle --dart-define=API_KEY=abcdefghijkl

Accessing the Value in Dart

Now, in your Flutter code, you can read this value using String.fromEnvironment:

class AppConstants {
  AppConstants._();

  static const String apiKey = String.fromEnvironment(
    'API_KEY',
    defaultValue: '',
  );
}

💡 Pro tip: The defaultValue parameter acts as a fallback in case you forget to pass the --dart-define flag during the build. This prevents your app from crashing, though you might want to handle empty values gracefully in production.


🍺 But Wait... What If I Have 10 Keys?

Good question! And yes, just grab your beer 🍺—oops, I mean coffee ☕—my bad.

If you have multiple secrets, passing them all via the command line gets tedious fast. Let's say you have 5 keys:

class AppConstants {
  AppConstants._();

  static const apiKey1 = "abc....";
  static const apiKey2 = "def....";
  static const apiKey3 = "ghi....";
  static const apiKey4 = "jkl....";
  static const apiKey5 = "mno....";
}

Nobody wants to type --dart-define five times every single run. Here's where shell scripts come to the rescue!


🚀 The Shell Script Solution

Create a shell script in your project's root directory. Let's call it run_dev.sh:

#!/bin/bash

flutter run --release \
  --dart-define=API_KEY_1=abc..... \
  --dart-define=API_KEY_2=def..... \
  --dart-define=API_KEY_3=ghi..... \
  --dart-define=API_KEY_4=jkl..... \
  --dart-define=API_KEY_5=mno.....

Then, in your Flutter code, access each key individually:

class AppConstants {
  AppConstants._();

  static const String apiKey1 = String.fromEnvironment(
    'API_KEY_1',
    defaultValue: '',
  );
  
  static const String apiKey2 = String.fromEnvironment(
    'API_KEY_2',
    defaultValue: '',
  );
  
  static const String apiKey3 = String.fromEnvironment(
    'API_KEY_3',
    defaultValue: '',
  );
  
  static const String apiKey4 = String.fromEnvironment(
    'API_KEY_4',
    defaultValue: '',
  );
  
  static const String apiKey5 = String.fromEnvironment(
    'API_KEY_5',
    defaultValue: '',
  );
}

Running the Script

Navigate to your project root and run:

./run_dev.sh

Getting a permission error? Make the script executable first:

chmod +x ./run_dev.sh

Now try again. Voilà! 🎉


🔒 Don't Forget: Update Your .gitignore

This is critical. If you commit your shell script with all your secrets, we're right back where we started—secrets in version control.

Add the script to your .gitignore file:

# Environment scripts with secrets
run_dev.sh
run_prod.sh

If you skip this step, all our security measures will fall into water... cos(90°) + log(1) = 0 + 0 = absolutely nothing. 😅


💡 Sound Familiar?

If you're coming from web development, you're probably thinking: "This sounds exactly like .env files!"

And you're absolutely right! The concept is the same:

  • Keep secrets outside your source code
  • Inject them at build/runtime
  • Never commit them to version control

Flutter's --dart-define is essentially the mobile equivalent of .env files in web development.


📦 Bonus: Using --dart-define-from-file

Starting from Flutter 3.7, there's an even cleaner approach! You can define all your environment variables in a JSON file:

Create a file called env.json (don't forget to add it to .gitignore!):

{
  "API_KEY_1": "abc.....",
  "API_KEY_2": "def.....",
  "API_KEY_3": "ghi.....",
  "API_KEY_4": "jkl.....",
  "API_KEY_5": "mno....."
}

Then run:

flutter run --dart-define-from-file=env.json

Much cleaner, right? This reads all your variables from a single file instead of cluttering your command line.


🎯 Key Takeaways

  1. Never hardcode secrets in your Dart files—they'll end up in git history forever.
  2. Use --dart-define to pass secrets at compile time.
  3. Access values with String.fromEnvironment() in your code.
  4. For multiple secrets, use shell scripts or --dart-define-from-file.
  5. Always add your secret files to .gitignore—no exceptions!

🙏 Thanks for Reading!

Thanks for reading my gibberish! I hope this helps you level up your Flutter security game. If you found this useful, feel free to share it with your fellow Flutter developers.

Happy coding, and remember: keep your secrets... secret! 🤫

Stop Hardcoding Secrets in Flutter – Use --dart-define Instead | Sidney Okine