dart_define
| Package | Kind | Provides the role | Needs | Choose with |
|---|---|---|---|---|
smf_dart_define | infrastructure | Values in .env | nothing | -m dart_define |
Some modules need values that only you have, such as the public key of a service or the address of a page. They name them as keys, and the role of the values in .env writes the keys into .env at the root of the app, where you fill them in; see Values in .env. dart_define gets those values into the app with --dart-define-from-file, an option of the flutter tool. It adds no package to the app.
A command of the flutter tool that builds, runs or tests the app reads .env when it starts, given --dart-define-from-file=.env, and compiles each value into the app as a constant. Nothing is generated after you edit .env. The analyzer sees the values as empty, so a fresh clone of the app analyzes and builds before anyone fills in .env.
What it adds to the app
| In the app | What it is |
|---|---|
lib/core/env/app_env.dart | AppEnv, with a static getter for each key of the app. An app whose modules give no key gets the class without members. |
.vscode/launch.json | The run configuration of VS Code, named after the app, which passes --dart-define-from-file=.env. |
.run/main.run.xml | The run configuration of Android Studio, named after the app, which passes the same argument. |
| The section "Run, test and build with .env" of the README | The commands with the argument, the run configurations, when the values are read, and how to add a getter. |
Whichever module provides it, the role adds .env, with a comment and an empty line for each key, .env.example, with the same keys, a line of .gitignore that keeps .env out of git, and the section "Values in .env" of the README, which lists the keys of the app.
A getter reads its value with String.fromEnvironment and returns null while the value is empty. KEY= in .env defines an empty value rather than none, so an empty line of .env and a missing one look the same to the app. This is a getter of an app with revenuecat:
abstract final class AppEnv {
/// The public API key of the App Store app in RevenueCat, which starts with
/// appl_.
static String? get revenuecatAppleKey {
const value = String.fromEnvironment('REVENUECAT_APPLE_KEY');
return value.isEmpty ? null : value;
}
}
The value is a constant of the build, so it is there before any code of the app runs, main() included, and a test reads it without starting the app.
In the guide for coding agents of the app, the role describes AppEnv under "Values in .env". The module adds the commands with the argument, the run configurations, the restart after an edit of .env, and how the getter of a new key reads its value.
Run, test and build
Give the argument to every command of the flutter tool that builds, runs or tests the app:
flutter run --dart-define-from-file=.env
flutter test --dart-define-from-file=.env
flutter build apk --dart-define-from-file=.env
flutter build appbundle --dart-define-from-file=.env
flutter build ipa --dart-define-from-file=.env
Without it, every getter of AppEnv is null, as for a .env with no values. The last line of smf create is still flutter run: right after the app is generated every value of .env is empty, so the app is the same without the argument.
A command reads .env only when it starts. After you change .env, stop flutter run and start it again. Hot reload and hot restart keep the values that the app was built with.
The run configurations pass the argument for you:
| IDE | What passes it |
|---|---|
| VS Code | .vscode/launch.json. Its configuration is the template of every launch of a file of the app ("templateFor": ""), so Run and Debug (F5), the Run and Debug links above main() and above a test, and the Testing view all pass it. |
| Android Studio | .run/main.run.xml, the run configuration named after the app. A test that you run from Android Studio gets no argument, so run tests from a terminal with the argument. |
.env stays out of git. A machine that builds the app from a clone, such as a CI runner, needs a .env of its own before the build: copy .env.example and fill it in, or write it from the secrets of the CI.
A key of your own
Add the key to .env.example and .env, under a comment that says what goes there, and give AppEnv a getter for it, as for the other keys:
static String? get myServiceKey {
const value = String.fromEnvironment('MY_SERVICE_KEY');
return value.isEmpty ? null : value;
}
The getter is the key in lowerCamelCase. Write the name of the key as a literal in String.fromEnvironment: the value is read when the app is compiled, so the name has to be a constant.
What the module does not do
- It loads nothing while the app runs. A change of
.envreaches the app with the next command that builds or runs it. - It reads one file,
.env. Another set of values, such as one for a staging backend, is another file that you pass yourself, as--dart-define-from-file=.env.staging. - It keeps no secret. Every value is compiled into the app, where anyone who has the app can read it, so
.envholds only values that may be public.
In tests
AppEnv reads constants, so a widget test reads a value without running main(). A test gets the values of .env only with flutter test --dart-define-from-file=.env, and gets null from every getter otherwise.
Whichever module provides the role, a getter returns the value that .env has for its key, and null for a key that .env leaves empty or lacks. SMF's CI checks this in running apps with the role: the test parses the .env of the app, in both forms that a value may take, and compares each key with its getter. With this module, the apps of revenuecat and of paywall run it with values of the tests in .env. See Contributing.
Choosing it
smf create adds the module when a module of the app needs values of .env and no other module provides them:
Adding dart_define: the only provider of the env role, which revenuecat requires.
To choose it yourself:
smf create my_app -m dart_define
An app with the module and no module that gives it keys gets .env without keys and AppEnv without getters, ready for keys of your own.