/compile-time variables/flutter/Mobile development
Flutter 1.17 — no more Flavors, no more iOS Schemas. Command argument that changes everything
Just recently, Flutter 1.17 was released and it brings a lot of cool features. It comes with lots of performance improvements, new Widgets, and more. But, besides all that, this version of Flutter also got one small, but very useful feature: compile-time variables. Yes, you heard me, starting from 1.17 you can build your Android and iOS application with predefined, compile-time variables that can be used in all layers of your applications — Dart, iOS, Android.
Let’s take a look closer
If you are working with Flutter Web, you may know the Flutter Tool argument --dart-define. Primarily it was used to enable Skia for the Flutter Web like this --dart-define=FLUTTER_WEB_USE_SKIA=true . But what you may not know, is that starting from 1.17 you can pass multiple custom key/value pairs there.
For example, if you run the flutter app with the following arguments
flutter run --dart-define=SOME_VAR=SOME_VALUE --dart-define=OTHER_VAR=OTHER_VALUE
then you can get these values in your Dart code like this
Short answer — yes, you can use these variables in native too but… it’s a little bit trickier.
If you are curious how Flutter passes Dart Defines into the native layers, you can take a look at the Flutter Tools package. It treats Dart Defines in a slightly different way for each platform but, I will show you examples for iOS and Android builds.
To do so, I created a Flutter project that defines the Application Name and Application Suffix ID for Android and iOS based on Dart Defines variables.
Let’s explore this application
This app works with just two compile-time variables which is DEFINEEXAMPLE_APP_NAMEandDEFINEEXAMPLE_APP_SUFFIX. I would recommend adding a prefix to every compile-time variable just to avoid conflicts with variables that Flutter uses. In this example I’m using DEFINEEXAMPLE_*.
You can notice that I used awesomeAppas a default value for DEFINEEXAMPLE_APP_NAME. We’re going to use this default value in all layers, including native. As for APP_SUFFIX — we will get an actual Application ID (packageName) in the FutureBuilder Widget from package_info plugin. And if DEFINEEXAMPLE_APP_SUFFIXisdefined, you will see the Application ID on your screen.
One important note: ensure that you are assigning environment variables to the const fields. Currently, Flutter has an issue if non-const variable was used.
When you run this application with a command like
flutter run --dart-define=DEFINEEXAMPLE_APP_NAME=awesomeApp1 --dart-define=DEFINEEXAMPLE_APP_SUFFIX=.dev
You should see next:
You can notice that it rendered awesomeApp1, which was passed from the arguments instead of the default awesomeApp. And com.example.defineexample.dev . Where .dev — defined Suffix.
Ok, so to pass compile-time variables into Android we will need to use Flavors.
No worries! No Flavors, no tons of iOS Schemes, no multiple entries, and no copy/paste… well, except default values for different layers. 🤷🏻♂
So, for Android, Flutter Tool passes all Dart Defines as a single string value, joined with a comma. So, in your Gradle, you will get a String value like: DEFINEEXAMPLE_APP_NAME=awesomeApp1,DEFINEEXAMPLE_APP_SUFFIX=.dev
So we will need to parse it before we can use each key-value pair. Full gradle code can be found here. Flutter passes Dart Defines in the project properties with dart-defines key:
First, it defines defaults (lines 1–4), then, converts a string from dart-defines into Map and merges the result with defaults. So now we can define resValue and applicationIdSuffix.
After we defined String resource it can be used for example in your android/app/src/main/AndroidManifest.xml to specify Application Name. But basically you can use this set of values anywhere in your project.
And now if you take a look at your application on the Android device, you can notice that application name was successfully defined:
Also as Package Name
During iOS build, Flutter Tool creates a couple of files, including Generated.xcconfig and flutter_export_environment.sh. They can be found in ios/Flutter. Those files are in gitignore by default, so GitHub project won’t contain them. But after the build, you will find Dart Defines in those files, defined like this:
Since this format can’t be used for plist, to define Application Name and Application ID Suffix, we need to define new variables, based on Dart Defines. So let’s start.
Important Note About *.xcconfig
iOS *.xcconfig has some limitations when it comes to variable values. You can read about it here or google for a probable solution. But basically, it treats the sequence // as a comment delimiter. This means that you won’t be able to specify API_URL for example like this https://example.com since everything after // will be ignored. In the same time, if you run your build with the following argument:
Flutter Tools will create flutter_export_environment.sh file with the following row:
So in your build Pre-Action (you will read about it in the next paragraph), you can try to figure out a workaround how to get actual value of DART_DEFINES to provide proper parsing of API_URL if you want to.