How to setup dart-define for keys and secrets on Android and iOS in Flutter apps

Gildásio Filho
Flutter Community
Published in
4 min readNov 24, 2021

Hello! I am Gildásio, a Flutter developer since before the first beta version and I’m here today to help you setup --dart-define on your project! Be sure to be running Flutter 2.5.3 or newer, since this won’t cover older versions.

Updated on 2022–09–22: Added a section explaining how to get and use a dart-define value on your MainActivity.kt

But What Am I Defining?

Good question, and I'm going to answer it in the most straightforward way possible. It's a way to forward environment variables to your Flutter app run or build. Which means you can customize your code with any value you passed through these parameters, or just use it in the most common way: just having an alternative mean to pass API keys and secrets to your app without having them hard-coded into your code, this way you can use those keys in a CI environment or just change as needed for app flavors. I'll be focusing in this specific usage for them since the other articles already cover flavors and changing your app's name and package, etc. Check Denis' version for that!

As of security, unfortunately, --dart-define is just as secure as having the keys on your code either way, as Rob shows us here that the keys are still bundled in the artifact: https://stackoverflow.com/a/69349448

Great, What Do I Have To Do?

First, remember that they are compile-time and will only be available if you explicitly set them during your flutter build or flutter run commands, just like this flutter run --dart-define API_KEY="asdfghjkl". If any of the steps below aren't working, check if you're actually passing them during your builds and runs.

Great, now that you passed the API_KEY environment variable through --dart-define, how do you access it inside your Dart code? Simple! Create a class with everything you need as static const values. They have to be static const, or Dart won't be able to fill them in during compilation.

That's the easy part, if you have any dependencies that only need your API key/secret inside your Flutter code, that's great! But most of them will still need you to set something inside either AndroidManifest.xml or AppDelegate.swift, or even both sometimes! When using them on Android or iOS, we first need to convert them from a base64 format, and only then we'll have the required values. Let's take a look on how to do that for each platform.

Android

Head over to android/app/build.gradle and we'll be using the following code to parse every available variable from --dart-define and make them available as a key:value map to use in all sorts of ways.

With this, we successfully converted the base64 versions of every pair, now how to use them? There's three options, explained below.

If you’re using something that requires a AndroidManifest.xml entry like below:

<meta-data
android:name="com.very.secret.api"
android:value="${API_KEY}" />

Add the following to your defaultConfig block, which will replace any ${} on your manifest with the contents of the API_KEY variable.

manifestPlaceholders += [
API_KEY: dartEnvironmentVariables.API_KEY
]

If you need it on a resource file:

<string name="com.very.secret.api" translatable="false">@string/api_key</string>

Or as a initializer on your MainActivity.kt file:

var apiKey = getString(R.string.api_key)
VerySecretApi.start(apiKey)

Add this line to your defaultConfig block, which will create a new resource value available on the static R class, which you can use like the examples above:

resValue "string", "api_key", dartEnvironmentVariables.API_KEY

These are only the most common ways that dependencies will require your keys, but you can use basically anything from dartEnvironmentVariables and set the app package suffix for example.

iOS

Again, we're focusing on how to handle API keys and secrets, so this will be quite different from what you've seen in the other articles that teach you how to change the app name and package. It's less work, but at the same time more limited in scope.

First, add the following to your ios/Runner/Info.plist file:

<key>DART_DEFINES</key>
<string>$(DART_DEFINES)</string>

This is so we can access the content of the raw --dart-define final value inside our Swift code. The other method using .xcconfig makes the values only available inside Xcode instead. Then, add this to your didFinishLaunchingWithOptions override:

It's equivalent to what we added in build.gradle above, it takes the raw values and converts them from base64 into a key:value dictionary that we can use to setup any SDKs as follows:

VerySecretApi.start(withApiKey: dartDefinesDictionary["API_KEY"]!, in:application, withLaunchOptions:launchOptions)

That's it! Now you can use any SDKs, APIs, plugins, inside your native code without worrying that your keys are exposed inside your code, filling them as needed on your CI environment, and be sure that they'll be available whenever and however you need! (at least until this changes somehow in a future Flutter update?)

Final words

Thanks for reading! I wrote this mainly because there's no other tutorial explaining how to do this in iOS, and I came up with the AppDelegate.swift code by myself when we encountered a SDK that needed to be initialized there because the didRegisterForRemoteNotificationsWithDeviceToken override needed the SDK to be started, and we were doing it "too late" inside the Flutter plugin.

I hope this helped you somehow, and if you have any tips or suggestions feel free to comment!

--

--