70

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
  • 7,058
  • 1
  • 33
  • 40

7 Answers7

94

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
  • 11,418
  • 10
  • 50
  • 76
tatsuDn
  • 1,359
  • 8
  • 15
  • 9
    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
  • 2
    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
  • 2
    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
28

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,072
  • 1
  • 12
  • 21
  • 11
    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
26

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
  • 547
  • 4
  • 11
6

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
  • 1,882
  • 2
  • 16
  • 20
6

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
  • 13,650
  • 4
  • 61
  • 95
1

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,242
  • 2
  • 21
  • 23
-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
  • 175,842
  • 51
  • 533
  • 380
  • 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