Setting environment variables in Flutter

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 :)

80387 次浏览

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.

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:

https://twitter.com/FlutterDev/status/1048278525432791041

https://github.com/flutter/flutter/issues/4014

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.

Here is an article that explains more.

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;

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/

https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ManagingSchemes.html

To run your app (in flutter run)

  • flutter run --dart-define=EXAMPLE_API_ENDPOINT=https://api.example.com/

To release your app (in flutter build)

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.

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 加载环境变量一次,并使用服务定位器(如GetIt)通过应用程序访问它们.

although above answers are correct coming from python and reactjs I used dotenv and found the same for flutter to load .env file https://pub.dev/packages/dotenv