Coming from the web I was used to define and use environmental variables in my apps. Typically those are defined inside a .env
file.
How to achieve the same result in Flutter? In dart the process is different. After some research, I was able to implement a complete setup for environmental variables in a Flutter app.
In this post we are going to learn:
- How and where to define environmental variables in dart
- How to access these variables in the app
- How to setup different environmental variables for different environment (production, development, testing, etc.)
Why should I use environmental variables
Why would you need environmental variables at all?
The primary usecase for environment variables is to limit the need to modify and re-release an application due to changes in configuration data.
Some examples of environmental variables are keys, URLs, domain names, email addresses.
What these have in common is that their values change infrequently. The application logic treats them like constants, rather than mutable variables.
Secret keys as environmental variables
The typical usecase for environmental variables is for storing and accessing secret keys in your app.
Every API requires to pass a secret key with each request to validate your identity. These keys should remain secret; they should not be pushed to a remote repository.
The most basic approach would be to define these variables as global constants in your app, directly inside a .dart
file:
/// List of shared keys in the app.
const key1 = "";
const key2 = "";
Nonetheless, this method is not ideal for the following reasons:
- Every member of the team needs to create the
keys.dart
file in his local machine, since this file cannot be stored in a shared repository - These keys are static, they cannot be switched based on the app configuration or environment
- Every time an environmental variable changes we need to access the source code
That is why dart has a built-in alternative for this!
Environmental variables in dart
The String
class in dart has a static method String.fromEnvironment
. This method allows to access the variables defined using the --dart-define
flag.
For example, when you run the build
command, you can add multiple --dart-define
flags to assign a value to each environmental variable:
flutter build appbundle -t lib/main_prod.dart --dart-define=API_BASE_URL=http://www.sandromaglione.com
When we run this command our app has access to the API_BASE_URL
using String.fromEnvironment('API_BASE_URL')
.
With this strategy is easy to define multiple variables for different environments (production, development, testing, etc), directly from the command line.
Defining environmental variables from the command line is ideal for testing different configurations in a CI/CD pipeline.
Using environmental variables in Flutter
The first step to access environmental variables in dart is to define an abstract
class that stores a constant for each variable in the app.
In this example, we are going to see how to define environmental variables for a Supabase project. A Supabase app needs to access two variables:
SUPABASE_URL
: The source URL of your Supabase projectSUPABASE_ANNON_KEY
: The key used to make request to your project
In order to access these two variables, we create a constants.dart
file with the following content:
/// Environment variables and shared app constants.
abstract class Constants {
static const String supabaseUrl = String.fromEnvironment(
'SUPABASE_URL',
defaultValue: '',
);
static const String supabaseAnnonKey = String.fromEnvironment(
'SUPABASE_ANNON_KEY',
defaultValue: '',
);
}
By defining these variables as static const
we can access them by calling Constants.supabaseUrl
and Constants.supabaseAnnonKey
.
The class is defined as
abstract
because it should not be possible to create an instance of this class (you cannot doConstants()
).
Define environmental variables when launching the app
The second step is defining those variables when we run our app.
As we have seen above, we just need to add the --dart-define
flag to our run
(or build
) command:
flutter run lib/main.dart --dart-define=SUPABASE_URL=url --dart-define=SUPABASE_ANNON_KEY=key
Make sure to spell the name of the variables correctly: this should be the same name used when calling
String.fromEnvironment
.
There is more 🤩
Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.
How to add a launch.json
file in vscode
If you are using vscode as IDE, you can create a launch.json
file. This file contains the command run by the IDE when starting your app.
Create a .vscode
directory and a launch.json
file inside it. You can then add the following to run your app using environmental variables:
{
"version": "0.2.0",
"configurations": [
{
"name": "Production",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
"args": [
"--dart-define=SUPABASE_URL=url",
"--dart-define=SUPABASE_ANNON_KEY=key"
]
}
]
}
How to configure different environments
As stated before, our goal is to define different environments with different variables for every configuration.
Using --dart-define
this becomes super easy. We can just add a new entry to our launch.json
file:
{
"version": "0.2.0",
"configurations": [
{
"name": "Production",
"request": "launch",
"type": "dart",
"program": "lib/main_prod.dart",
"args": [
"--dart-define=SUPABASE_URL=url",
"--dart-define=SUPABASE_ANNON_KEY=key"
]
},
{
"name": "Development",
"request": "launch",
"type": "dart",
"program": "lib/main_dev.dart",
"args": [
"--dart-define=SUPABASE_URL=url",
"--dart-define=SUPABASE_ANNON_KEY=key"
]
}
]
}
This two commands are used for the Production
and Development
environments. As you can see, we reference two different entry files: main_prod.dart
and main_dev.dart
.
We need to create those two files alongside the default main.dart
. The suggestion here is to keep the shared configuration inside main.dart
and defined environment-specific configurations inside main_prod.dart
and main_dev.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_supabase_complete/constants.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
/// Shared `runApp` configuration.
///
/// Used to initialize all required dependencies, packages, and constants.
Future<void> mainCommon() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: Constants.supabaseUrl,
anonKey: Constants.supabaseAnnonKey,
);
runApp(const App());
}
import 'package:flutter_supabase_complete/main.dart';
/// Perform extra configuration required for production environment.
Future<void> main() async {
await mainCommon();
}
Environmental variables using a package
You can also use an external package to import environmental variables in your app.
There are many different solutions on pub.dev. The final goal is the same: define some shared global constants and access them in your Flutter app.
One example is envify. Envify allows you to import environmental variables directly from a .env
file.
This package uses build_runner to read the content of .env
and generate a class containing all your constants.
First of all, create a .env
file. This file contains a list of key-value pairs:
KEY1=value1
KEY2=value2
Then create a dart file (env.dart
) and define the expected environmental variables to generate:
import 'package:envify/envify.dart';
part 'env.g.dart';
@Envify()
abstract class Env {
static const String key1 = _Env.key1;
static const String key2 = _Env.key2;
}
Finally, run the generator to access the variables in your app:
flutter pub run build_runner build
This command will generate an env.g.dart
file:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'env.dart';
// **************************************************************************
// EnvifyGenerator
// **************************************************************************
class _Env {
static const String key1 = 'value1';
static const String key2 = 'value2';
}
As you can see, the end result is the same: we have an
abstract
class containing all our environmental variables.
That's it! Now you can leverage the power of environmental variables in your Flutter app.
Every member of the team should create a launch.json
file containing the correct --dart-define
configuration. You can then define different environments simply by creating a new main_*.dart
file and its corresponding entry inside launch.json
.
If this post was interesting, you can follow me on Twitter for daily updates and tips on Flutter and dart.
Thanks for reading.