# Values in .env

Some modules need values that only you have, such as the public API key of a service or the address of a page. A module names each value as a key, and the role of the values in `.env` puts the keys of every module of the app into `.env` at the root of the app. You fill in the values there, and code reads them through `AppEnv`.

| Part | Role | Module | What it brings |
| --- | --- | --- | --- |
| Values in .env | Values in .env | [`dart_define`](https://doc.saymyframe.com/modules/dart-define.md) | `AppEnv`, whose getters return the values of `.env`, compiled into the app with `--dart-define-from-file`. |

A module that needs such values brings `dart_define` with it while that module is the only one that provides the role, as [`revenuecat`](https://doc.saymyframe.com/modules/revenuecat.md) and [`paywall`](https://doc.saymyframe.com/modules/paywall.md) do:

```bash
smf create my_app -m paywall,bloc
```

| File | From | What it holds |
| --- | --- | --- |
| `.env` | the role | A comment and an empty line `KEY=` for each key of the app. You fill in the values here. It stays out of git. |
| `.env.example` | the role | The same keys without values. It stays in git, so a clone of the app knows which values to fill in. |
| `.gitignore` | the role adds a line | `.env`, under a comment that tells where its values come from. |
| `lib/core/env/app_env.dart` | the module that provides the role | `AppEnv`, with a static getter for each key. |

Right after `smf create`, every value is empty, and the app builds and starts. A module whose value is missing works without it as far as it can: [`revenuecat`](https://doc.saymyframe.com/modules/revenuecat.md) runs the app without purchases, and [`paywall`](https://doc.saymyframe.com/modules/paywall.md) leaves out a link.

## Write a value

```bash title=".env"
# The public API key of the Test Store in RevenueCat, which starts with test_, for debug builds only
REVENUECAT_TEST_KEY=test_AbCdEf123

# The address of the terms of use that the paywall links to
PAYWALL_TERMS_URL="https://example.com/terms#refunds"
```

| You write | The app reads |
| --- | --- |
| `KEY=value`, a plain value without `#`, spaces or quotes | `value` |
| `KEY="any value"`, in double quotes | `any value`. A value with a space, a `#` or a quote goes in double quotes. |
| `KEY=value # a comment` | `value`. After a plain value, `#` starts a comment, so an address with an anchor needs the quotes. |
| `KEY=` | `null`. An empty value counts as missing. |
| A line that starts with `#` | Nothing: it is a comment. |

The comment at the top of `.env` and `.env.example` says this in one line. Every module that provides the role reads both forms of a value alike.

After a clone of the app, copy the example and fill it in:

```bash
cp .env.example .env
```

:::warning
Every value ends up in the app, where anyone who has the app can read it. So `.env` holds only values that may be public, such as the public key of a service or the address of a page. Never put a secret there, such as the secret key of a server, a password or a private key.
:::

## Read a value

Code reads a value only through the static getter of its key in `AppEnv`. The getter is the key in lowerCamelCase, and returns a `String?`, which is `null` while the value is empty or missing:

```dart
import 'core/env/app_env.dart';

final terms = AppEnv.paywallTermsUrl;
if (terms != null) showTermsLink(Uri.parse(terms));
```

A getter returns its value at once, before any code of the app runs, `main()` included, so code reads it anywhere without waiting, and so does a test that does not start the app. Handle `null`: a value that nobody filled in yet is no error. Never read `.env` or `.env.example` from code.

The keys of the built-in modules:

| Key | Getter | Module | What goes there |
| --- | --- | --- | --- |
| `REVENUECAT_APPLE_KEY` | `AppEnv.revenuecatAppleKey` | [`revenuecat`](https://doc.saymyframe.com/modules/revenuecat.md) | The public API key of the App Store app in RevenueCat, which starts with `appl_` |
| `REVENUECAT_GOOGLE_KEY` | `AppEnv.revenuecatGoogleKey` | [`revenuecat`](https://doc.saymyframe.com/modules/revenuecat.md) | The public API key of the Play Store app in RevenueCat, which starts with `goog_` |
| `REVENUECAT_TEST_KEY` | `AppEnv.revenuecatTestKey` | [`revenuecat`](https://doc.saymyframe.com/modules/revenuecat.md) | The public API key of the Test Store in RevenueCat, which starts with `test_`, for debug builds only |
| `PAYWALL_TERMS_URL` | `AppEnv.paywallTermsUrl` | [`paywall`](https://doc.saymyframe.com/modules/paywall.md) | The address of the terms of use that the paywall links to |
| `PAYWALL_PRIVACY_URL` | `AppEnv.paywallPrivacyUrl` | [`paywall`](https://doc.saymyframe.com/modules/paywall.md) | The address of the privacy policy that the paywall links to |

The keys are in `.env` in the order of the modules of the app. The section "Values in .env" of the README of the app lists the keys of that app, each with its getter, its module and what goes there.

## Get the values into the app

How the values get into the app is up to the module that provides the role. With [`dart_define`](https://doc.saymyframe.com/modules/dart-define.md), the `flutter` tool reads `.env` when a command that builds, runs or tests the app starts, given `--dart-define-from-file=.env`:

```bash
flutter run --dart-define-from-file=.env
```

Without the argument, every getter is `null`. The run configurations of VS Code and Android Studio pass it for you, and a change of `.env` reaches the app once you start `flutter run` again; see [`dart_define`](https://doc.saymyframe.com/modules/dart-define.md#run-test-and-build).

## A key of your own

Put the key into both `.env.example` and `.env`, in UPPER_SNAKE_CASE, with a comment line above it that says what goes there. Then give `AppEnv` a getter for it, named by the key in lowerCamelCase, as the module that provides the role writes the others. The section of that module in the README of the app shows the getter.

## In a module of your own

A module that needs a value gives the role a key, an `EnvKey` with a name and a description, and lists the role among the roles it requires or uses:

```dart
static const apiKey = EnvKey(
  'WEATHER_API_KEY',
  description: 'The public API key of the weather service',
);
```

- The name is in UPPER_SNAKE_CASE, of two words or more, and starts with the id of the module in upper case and an underscore, such as `WEATHER_` for a module `weather`. It never starts with `FLUTTER_`.
- The description is one line of at most 100 characters. It becomes the comment of the key in `.env` and the doc of its getter.
- A brick of the module reads the value through a variable of `envRole.varsOf(id, keys)`, named `env_<key in snake_case>`, such as `{{{env_weather_api_key}}}`. It is `AppEnv.weatherApiKey` in an app with the role and `null` in an app without it, so the code takes a `String?` there and is no `const`.
- A module that requires the role may also read `AppEnv` in its own code.

The module rule `env.keys` checks the keys and the variables of a module. `env.key_access` checks that the files of a module read only its own keys and those of the modules it depends on, and `env.keys_rendered` that `AppEnv` has a getter for every key. The template of a role gives no key.

## For a provider

A module that provides the role generates `AppEnv` in `lib/core/env/app_env.dart`: an `abstract final class` with a static `String?` getter for each key of `envRole.keysIn(input)`. Its getters keep to the contract of the role:

- A getter returns the value that `.env` has for its key, or `null` when the value is empty or the key is missing.
- It returns it at once, before any code of the app runs. A provider that loads `.env` while the app runs, which takes a `Future`, or that sets the values in `main()`, does not keep the contract.
- A command of the `flutter` tool that builds, runs or tests the app gives `AppEnv` the values that `.env` has when the command runs, and nothing else runs after `.env` changes. A provider whose values need code generation after an edit of `.env` does not keep the contract either.

A provider that needs arguments on those commands gives them to the role as `EnvToolArguments`, as `dart_define` gives `--dart-define-from-file=.env`. Code that runs the commands, such as a run configuration or a test tool, takes them from `envRole.toolArgumentsIn(input)` without knowing what they mean.

## In tests

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, with a fixture provider, and with a broken provider that the test must fail on: one whose `AppEnv` returns an empty value as an empty text. See [Contributing](https://doc.saymyframe.com/contributing.md#the-roles-at-run-time).
