I was wondering while programming in general, but in this case, when I was developing a Dart package, a simple console app or a Flutter App, if there's a way to make the test line and the production line more automated.

So, for example, how can I test my program that needs some secret variables?

There are a lot of way that you can achieve that.
In my case, I'm used to put environment parameters into enviromental variables and pick them up in CI/CD pipeline when testing or building my software.

This approach is kinda simple, but may not work for others.
So, I just wanna share it.

The Problem

Let’s say that you have a typical Repository implementation where you have hardcoded the base URL. This will not cut it if you have multiple environments.

import 'package:http/http.dart' show Client;

class Repository {
  final String _baseUrl = 'some-url.com';

  Client _client;

  DebtorRepository() {
    this._client = Client();
  }
  
  // For simplicity's sake we will not deal with json encoding/decoding
  Future<any> info(String token) async {
    return await this._client.get('$_baseUrl/info');
  }
}

Config File

The next obvious step is to create a config file to hold the values of the variables in each environment.

final environment = {
  'baseUrl': 'some-url.com'
};

Well, this is better. You could even get away without using environmental variables if you just have a runtime resolution of the environment and use the appropriate configuration.

const bool isProduction = bool.fromEnvironment('dart.vm.product');

const testConfig = {
  'baseUrl': 'some-url.test'
};

const productionConfig = {
  'baseUrl': 'some-url.com'
};

final environment = isProduction ? productionConfig : testConfig;

Now you can use this configuration in your Repository.

import 'package:http/http.dart' show Client;

import 'env.dart';

class Repository {
  final String _baseUrl = environment.baseUrl;

  Client _client;

  DebtorRepository() {
    this._client = Client();
  }
  
  // For simplicity's sake we will not deal with json encoding/decoding
  Future<any> info(String token) async {
    return await this._client.get('$_baseUrl/info');
  }
}

Based on the previous example, there is no need to use any env vars at build time; so why do it? The answer is sensitive information such as API credentials and/or secrets.

Let’s say that your API requires some sort of secret credentials to be passed in order to authorize your app’s calls. In this case, putting this information into a file and storing it in your version control server is very much frowned upon. So you have to insert them in your application from your build environment.

const bool isProduction = bool.fromEnvironment('dart.vm.product');

const testConfig = {
  'baseUrl': 'some-url.test',
  'credentials': '', //This should not be defined here!
};

const productionConfig = {
  'baseUrl': 'some-url.com',
  'credentials': '', //This should not be defined here!
};

final environment = isProduction ? productionConfig : testConfig;

The script

You have to define appropriate environment variables for your credentials variable, let's say APP_CREDENTIALS. The problem now is that this variable is defined in the built environment and not in the runtime.

An idea is to generate this config file built time so that you can have access to the env vars. So, you have to create a script that will read certain env vars and put them in the config file.

According to the official docs of pub, such scripts need to be in a tool folder at the root of your app directory. The simplest way to write such a script is by creating a map with all the variables and their values and then just pass them into a template string.

import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  final config = {
    'baseUrl': Platform.environment['APP_BASE_URL'],
    'credentials': Platform.environment['APP_CREDENTIALS'],
  };

  final filename = 'lib/.env.dart';
  File(filename).writeAsString('final environment = ${json.encode(config)};');
}

Of course, this script can be more elaborate than this, but you are not trying to build a dedicated library here. You can put all of your environment configurations into env vars so that a change in them will not have to cause code changes.

How to use it

In order to use this facility now, all you have to do is to define the env vars in your CI/CD environment and to make sure to run the following command before you build the application

$: dart tool/env.dart

There are a lot of ways to achieve env var insertion into a flutter build and by no means do I claim that this is the best, but this was something I came up with in order to solve this problem for me. Another option could have been to use a build_runner and build the .env.dat in a more structured way than a simple string template.


What can I say... I am @Nebulino... I like Anime, the Android world and all kawaii stuff... Don't think something strange... I like to dev too!