Setting environment variables in Flutter
Asked Answered
T

11

112

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

Tinkling answered 29/5, 2017 at 20:55 Comment(1)
Related: #47439064Pace
E
163

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.

Ensoul answered 11/5, 2020 at 8:21 Comment(11)
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=)Alexi
Is it possible to access this environment variables inside the AppDelegate.swift?Dupin
@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 thereEnsoul
@JoseTapizquent Yes you can. Check this answer https://mcmap.net/q/195892/-hide-google-maps-api-key-from-source-control-in-a-flutter-appNovokuznetsk
Be aware the const is a requirement! see the article in the answer and github.com/flutter/flutter/issues/55870 I was having trouble getting this working since I didn't realize thatStandardbearer
On, MacBook Air (M1, 2020) with flutter env as vars, this doesn't work. Flutter 2.0.6 • channel stable • 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.3Decemvirate
How would you bake these environment variables into your app when building for production? e.g. --dart-define=ENVIRONMENT=PRODMonongahela
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 argumentsEnsoul
setting multiple env variables using comma separated approach did not work. flutter build web --dart-define=env=dev,domain=xyz - this did not work.Herald
I love how the question is: Setting environment variables in Flutter, yet all answers just point how to set --dart-define, WHICH ARE NOT ENV variables in the classical sense. If you use some FFI they will not see those env variables....Dally
didn't worked on linuxSchutzstaffel
F
36

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.

Feline answered 1/6, 2017 at 0:31 Comment(1)
If anyone's interested, I've put together a sample app demonstrating how this can be done https://github.com/ROTGP/flutter_environmentsGuardafui
F
31

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

Fye answered 16/4, 2019 at 21:49 Comment(0)
T
16

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.
Templar answered 22/12, 2021 at 22:16 Comment(4)
Please note: This is not a secure way in case you want to store api keys. Look hereTypify
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.Templar
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.Templar
@Typify 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.Suture
I
14

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');
Irresolution answered 17/3, 2023 at 10:42 Comment(1)
This does not work really anymore, see this issue: github.com/flutter/flutter/issues/138793Argenteuil
P
13

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

Pasty answered 3/11, 2021 at 15:49 Comment(2)
How to use flutter attach and also use breakpoints for debugging using this approach? @udarawanasingheHalpern
Can you please provide an example of .env fileHousewife
B
8

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

Bran answered 26/5, 2023 at 18:6 Comment(0)
T
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.

Tails answered 12/5, 2022 at 9:23 Comment(3)
you can also use the flutter_dotenv library from pub.dev: pub.dev/packages/flutter_dotenvThaumatology
Thanks @Samuel Nde, but how are you going to change the API Key in production without affect app version or build processLiteralism
@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.Tails
F
5

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.

Forestay answered 29/4, 2023 at 20:4 Comment(0)
M
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

Microampere answered 27/9, 2022 at 10:7 Comment(1)
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.Standardize
G
-3

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;
Glaab answered 29/9, 2020 at 11:7 Comment(2)
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.Kelcie
@Kelcie I definitely agree with you, it's not suitable in such cases.Glaab

© 2022 - 2024 — McMap. All rights reserved.