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.dartfile 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.comWhen 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 project
- SUPABASE_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
abstractbecause 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=keyMake sure to spell the name of the variables correctly: this should be the same name used when calling
String.fromEnvironment.
There is more 🤩
Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights
Not convinced? Well, let me tell you more about it
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=value2Then 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 buildThis 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
abstractclass 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.
