Setting environment variables in Flutter
DartFlutterDart Problem Overview
For example, building a client for an API, like Twitch.
In a Dart CLI binary, I could use a generic environment variable, or a Dart definition variable. For example, using both as fallbacks:
main() {
String clientId =
// dart -dCLIENT_ID='abc bin/example.dart
// This is considered "compiled-into" the application.
const String.fromEnvironment('CLIENT_ID') ??
// CLIENT_ID='abc' dart bin/example.dart
// This is considered a runtime flag.
Platform.environment['CLIENT_ID'];
// Use clientId.
}
Does Flutter have a way of setting either/both of these, specifically...
- During dev time
- When shipped to prod
Happy to help with some docs once I figure out how :)
Dart Solutions
Solution 1 - Dart
Starting from Flutter 1.17 you can define compile-time variables if you want to.
To do so just use --dart-define
argument during flutter run
or flutter build
If you need to pass multiple key-value pairs, just define --dart-define
multiple times:
flutter run --dart-define=SOME_VAR=SOME_VALUE --dart-define=OTHER_VAR=OTHER_VALUE
and then, anywhere in your code you can use them like:
const SOME_VAR = String.fromEnvironment('SOME_VAR', defaultValue: 'SOME_DEFAULT_VALUE');
const OTHER_VAR = String.fromEnvironment('OTHER_VAR', defaultValue: 'OTHER_DEFAULT_VALUE');
Also, they can be used in native layers too.
Solution 2 - Dart
For configuration a common pattern I've seen is to use separate main files instead. i.e.
flutter run -t lib/production_main.dart
and
flutter build apk -t lib/debug_main.dart
And then in those different main files set up the configurations desired.
In terms of reading ids, you can do that from arbitrary assets https://flutter.io/assets-and-images/.
I believe it is possible in Flutter to read from the environment as you suggest, however I don't know how to set those environment variables on iOS or Android.
Solution 3 - Dart
Since I was trying to solve this as well and encountered this thread I just wanted to add this for people looking for a solution in the future... If all you're looking for is PROD/DEV environments there is now a supported way of getting if the app is in production or not:
const bool isProduction = bool.fromEnvironment('dart.vm.product');
As suggested by:
Solution 4 - Dart
I use simple shell script to generate dart defines. In my app there are 3 build flavors: dev
, staging
and prod
. Environment variables were defined in a regular .env
file.
env/
├── dev.env
├── prod.env
└── staging.env
Here is the script to generate dart-defines
from .env
file.
#!/bin/bash
# scripts/generate_dart_defines.sh
case "$1" in
"dev") INPUT="env/dev.env"
;;
"staging") INPUT="env/staging.env"
;;
"prod") INPUT="env/prod.env"
;;
*)
echo "Missing arguments [dev|staging|prod]"
exit 1
;;
esac
while IFS= read -r line
do
DART_DEFINES="$DART_DEFINES--dart-define=$line "
done < "$INPUT"
echo "$DART_DEFINES"
Here is the script to trigger a build.
#!/bin/bash
# build.sh
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo -e "Missing arguments: [apk|appbundle|ios] [release|debug|profile] [dev|staging|prod]"
# invalid arguments
exit 128
fi
DART_DEFINES=$(scripts/generate_dart_defines.sh $3)
if [ $? -ne 0 ]; then
echo -e "Failed to generate dart defines"
exit 1
fi
echo -e "artifact: $1, type: $2, flavor: $3\n"
echo -e "DART_DEFINES: $DART_DEFINES\n"
eval "flutter build $1 --$2 --flavor $3 $DART_DEFINES"
The script accepts 3 arguments. First one is the artifact apk
, appbundle
or ios
. Second one is the build type release
, debug
or profile
. Third one is the build flavor, dev
, staging
or prod
.
./build.sh apk release prod
Please note that you also required to configure android and ios for different build flavors separately. https://developer.android.com/studio/build/build-variants
https://shockoe.com/ideas/development/how-to-setup-configurations-and-schemes-in-xcode/
Solution 5 - Dart
flutter run
)
To run your app (in flutter run --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/
flutter build
)
To release your app (in My app wasn't letting users log in I realized that environment variables were empty strings in the app, instead of their actual values .
- iOS:
flutter build ipa --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/
- Android:
flutter build apk --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/
--dart-define
documentation
From the flutter run --help
or flutter build ipa --help
, the --dart-define
shows:
Additional key-value pairs that will be available as
constants from the String.fromEnvironment, bool.fromEnvironment,
int.fromEnvironment, and double.fromEnvironment constructors.
Multiple defines can be passed by repeating "--dart-define"
multiple times.
Solution 6 - Dart
I do agree
with the answer posted by @tatsuDn but I wanted to provide a solution that loads your environment variables from a .env file.
First create a .env
file in the root folder of your project.
Ensure that you add the file to your pubspec.yaml
and [git] ignore
it.
Here is how your .env
file should look
API_KEY=sampleapikey
# This line is a comment
# The white line above will be ignored
HEADER=sampleapiheader
ANOTHER_UNIQUE_KEY=theValueOfThisKey
KEY_CONTAINS_#=*234*5#
KEY_CONTAINS_EQUALS=IP8iwe=0&
Here is how your assets section to look like.
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
- assets/flags/
- .env
Finally, load your environment variable by reading and parsing the .env
file to get a Map<String, String>
that contains your key value pairs.
Future<Map<String, String>> parseStringToMap({String assetsFileName = '.env'}) async {
final lines = await rootBundle.loadString(assetsFileName);
Map<String, String> environment = {};
for (String line in lines.split('\n')) {
line = line.trim();
if (line.contains('=') //Set Key Value Pairs on lines separated by =
&&
!line.startsWith(RegExp(r'=|#'))) {
//No need to add emty keys and remove comments
List<String> contents = line.split('=');
environment[contents[0]] = contents.sublist(1).join('=');
}
}
return environment;
}
You can put a quick button in your code to test that the environment variables are being loaded properly.
ElevatedButton(
onPressed: () async {
final env = await parseStringToMap(assetsFileName: '.env');
print(env);
},
child: Text('Print Environment Variables')
),
Here is the output from the .env file above.
>>>I/flutter ( 7182): {API_KEY: sampleapikey, HEADER: sampleapiheader, ANOTHER_UNIQUE_KEY: theValueOfThisKey, KEY_CONTAINS_#: *234*5#, KEY_CONTAINS_EQUALS: IP8iwe=0&}
Notes: You will need to rerun the app (not hot reload) so that the .env
assets is loaded.
You can also just load your variables in a json file[this may be helpful when you have non string environemental variables and you dont want to parse string.
To avaoid IO, it is a good Idea to just load the environment variables once and access them through out the app using service locators like GetIt
.
Solution 7 - Dart
Create a class:
import 'package:flutter/foundation.dart';
class AppUtils {
static String get clientId {
if (kDebugMode) return 'debug_id';
else if (kProfileMode) return 'profile_id';
else if (kReleaseMode) return 'production_id';
else if (kIsWeb) return 'web_mode_id';
throw ArgumentError('No mode detected');
}
}
Usage:
var id = AppUtils.clientId;