How to use Environmental Variables in Flutter

Sandro Maglione

Sandro Maglione

Mobile development

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:

keys.dart
/// 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 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:

constants.dart
/// 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 do Constants()).

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.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 600+ readers.

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:

launch.json
{
  "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:

launch.json
{
  "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:

main.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());
}
main_prod.dart
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:

.env
KEY1=value1
KEY2=value2

Then create a dart file (env.dart) and define the expected environmental variables to generate:

env.dart
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:

env.g.dart
// 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.

👋・Interested in learning more, every week?

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 600+ readers.