98

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

matanlurey
  • 8,096
  • 3
  • 38
  • 46

11 Answers11

149

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.

Yann39
  • 14,285
  • 11
  • 56
  • 84
tatsuDn
  • 2,019
  • 1
  • 9
  • 15
  • 19
    To pass multiple key-value pairs, you can use also this syntax: `--dart-define=FIRST_VAR=first_value,SECOND_VAR=second_value` (using the comma, without repeat `--dart-define=`) – Mabsten Dec 27 '20 at 20:27
  • 3
    Is it possible to access this environment variables inside the AppDelegate.swift? – tapizquent Jan 05 '21 at 03:47
  • @JoseTapizquent I haven't tried, but I can assume that you can always add your dart define value to the plist file and read from there – tatsuDn Jan 05 '21 at 18:56
  • @JoseTapizquent Yes you can. Check this answer https://stackoverflow.com/a/59590990/1843853 – Gabriel Gava Jan 14 '21 at 13:26
  • 8
    Be aware the `const` is a requirement! see the article in the answer and https://github.com/flutter/flutter/issues/55870 I was having trouble getting this working since I didn't realize that – aaronvargas May 31 '21 at 17:44
  • On, MacBook Air (M1, 2020) with flutter env as vars, this doesn't work. Flutter 2.0.6 • channel stable • https://github.com/flutter/flutter.git Framework • revision 1d9032c7e1 (5 months ago) • 2021-04-29 17:37:58 -0700 Engine • revision 05e680e202 Tools • Dart 2.12.3 – Keisuke Nagakawa 永川 圭介 Sep 13 '21 at 01:49
  • How would you bake these environment variables into your app when building for production? e.g. --dart-define=ENVIRONMENT=PROD – Justin Dec 13 '21 at 18:17
  • You can specify them during any build command execution. If you have a CI/CD configured on bitrise or codemagic, just add them to the command arguments – tatsuDn Dec 13 '21 at 22:21
  • setting multiple env variables using comma separated approach did not work. `flutter build web --dart-define=env=dev,domain=xyz` - this did not work. – Aseem Mar 03 '23 at 06:11
34

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.

Eric Seidel
  • 3,282
  • 1
  • 15
  • 22
  • 12
    If anyone's interested, I've put together a sample app demonstrating how this can be done [https://github.com/ROTGP/flutter_environments](https://github.com/ROTGP/flutter_environments) – hunter Aug 24 '18 at 15:03
30

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

Oreflow
  • 587
  • 4
  • 11
15

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.
Ben Butterworth
  • 22,056
  • 10
  • 114
  • 167
  • 1
    Please note: This is not a secure way in case you want to store api keys. Look [here](https://stackoverflow.com/a/69349448/14119471) – Binozo Jun 08 '22 at 10:55
  • It depends in what context. This doesn't "store" API keys, in CI you would pass the same variables which can be masked/hidden/protected. However, If someone hacked your app, they can extract the API key. You certainly don't want to put a powerful API key in your app. – Ben Butterworth Jun 08 '22 at 10:57
  • 1
    You shouldn't have valuable API keys on your app. It should be used on your server if you need to use 3rd party services (when they give you a powerful API key). Your apps would authenticate with your server and your **server makes requests on behalf of the client apps**. This allows you to authenticate, authorize and rate-limit abusive clients. – Ben Butterworth Nov 10 '22 at 11:34
  • @Binozo Regardless of how you pass API keys to the client... from a security point of view it is a hostile environment that you don't have control over. – Tom Raganowicz Jun 26 '23 at 08:40
12

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

UdaraWanasinghe
  • 2,622
  • 2
  • 21
  • 27
7

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.

Samuel Nde
  • 2,565
  • 2
  • 23
  • 23
  • 1
    you can also use the flutter_dotenv library from pub.dev: https://pub.dev/packages/flutter_dotenv – 1housand Aug 02 '22 at 03:54
  • Thanks @Samuel Nde, but how are you going to change the API Key in production without affect app version or build process – Danford Kija Nov 01 '22 at 08:33
  • @DanfordKija this solution will load whatever you put in your `.env`. So if you want different values for production and dev environments, the way to do it will be `to have a different .env file for production and testing environments respectively.` – Samuel Nde Nov 16 '22 at 07:34
7

If you have multiple environment variables, use option --dart-define-from-file=env.json.

Ex:

flutter build web --dart-define-from-file=env.json

or

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

Place env.json in the root, where pubspec.yaml resides.

Sample json files

  1. env.json

    { "backend_url": "https://server.com" }

  2. env_dev.json

    { "backend_url": "https://dev.server.com" }

Sample use:

const backendUrl = String.fromEnvironment('backend_url', defaultValue: 'SOME_DEFAULT_VALUE');
JohnIsowhiz
  • 71
  • 1
  • 2
5

If you're using the Flutter version >= 3.7 you can pass the environment variables in 2 ways either by argument or by a config file. for example:

flutter run --dart-define=BASE_URL=http://localhost:3000

Or you can create a file such as env.json and set your all desired variables in it for example:

{
  "BASE_URL": "http://localhost:3000",
  "TEST_USER": "test_user"
}

and then pass the file:

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

And if your Flutter version is < 3.7 you have only the first option

2

Flutter introduced environment variables at compile-time using the --dart-define argument. Where you have more than one environment variable, using the --dart-define-from-file argument is advisable.

For a single environment variable, follow the below:

flutter run --dart-define=VAR_NAME=SOME_VALUE

Where there is more than one environment variable, follow the below steps:

  • Create a JSON file containing the variables.
{
    "VAR_A": someValue,
    "VAR_B": anotherValue
}
  • pass this file to the flutter build/run command
flutter run --dart-define-from-file=config.json

where config.json is the created JSON file containing the variables.

To retrieve these variables from your code, any of the following works depending on the data type:

const VAR_A = String.fromEnvironment('VAR_A', defaultValue: 'SOME_DEFAULT_VALUE');
const VAR_B = int.fromEnvironment('VAR_B', defaultValue: 1);
const VAR_C = bool.fromEnvironment('VAR_C', defaultValue: false);

It should be noted that there are no fromEnvironment constructors for double. The above arguments can also be used with the flutter build command.

The article here explains how to do this in-depth.

Codefarmer
  • 684
  • 4
  • 8
0

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

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 30 '22 at 20:35
-4

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;
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • 1
    I think this is a clean answer generally, but at least for me it won't fit my use case. In my case I want to show and detect Crashlytics errors during automated testing. So I want to use a flag to display exceptions that would be logged visually for debug mode in a CI/CD automated test pipeline so that the error can be detected during a test easily (logs cannot be as easily read via Appium). So I have multiple running environments for, say, debug mode. – RoboBear May 18 '21 at 08:58
  • @RoboBear I definitely agree with you, it's not suitable in such cases. – CopsOnRoad May 18 '21 at 08:59