🎯 Environments

Why do we need environments ?

Using an environment file allows you to manage and isolate configuration settings such as database connections, API keys, and other sensitive information, enhancing the security and flexibility of your application.

Environments are available in the 'lib/environnements.dart' file.
Create one factory for each in the lib/environnements.dart file.

...
const factory Environment.dev({required String name}) = DevEnvironment;
const factory Environment.production({required String name}) = ProdEnvironment;
...

By default our template provides 3 environments :

  • dev
  • staging
  • prod

These objects contains all the API keys and configuration for each environment.
Prefer using environment variables instead of hard coded values.
This is also great to have a file containing all the required configuration for your app.

Environment variables

To be able to compile our app with CI/CD we need to use environment variables.
Dart provides a way to pass environment variables to the app using the command line.
You can check it here

I encourage you to never call String.fromEnvironment('ENV') directly in your code. Instead you should create a property in your environment file to access your environment variables.

As we use Freezed for the environment object we are sure that it is Immutable.
No one can change any property at runtime.

Use environment variables in your code

First inject the EnvironmentProvider where you need. (Most of the time you will need it in your *_api.dart files)

final ratingApiProvider = Provider<RatingApi>((ref) {
  final prefsBuilder = ref.watch(sharedPreferencesProvider);
  return RatingApi(
    ...
    env: ref.watch(environmentProvider),
  );
});

Then you can access your environment variables using the environment object.

Future<void> openStoreListing() {
    if (_env.appStoreId == null) {
      throw Exception(
        '''appStoreId is not defined in your environment, check [environnements.dart] file''',
      );
    }
    return ...;
  }

How to add your own environment variable

You can add your own environment variable in the lib/environnements.dart file.

First add your property for each environment.

const factory Environment.dev({
    required String [MY PROPERTY],
    ...
}) = DevEnvironment;

Note: change [MY PROPERTY] with your property name and type.

As we use Freezed for immutability you need to regenerate the code using

dart run build_runner build --delete-conflicting-outputs 

Then update the factory constructor to add your property

...
factory Environment.fromEnv() {
    switch (_kEnvironmentInput) {
      case 'dev':
        return const Environment.dev(
          appStoreId: String.fromEnvironment('APP_STORE_ID', defaultValue: 'com.example.myapp.dev'),
          ...
        );
      case 'prod':
        return const Environment.dev(
          appStoreId: String.fromEnvironment('APP_STORE_ID'),
          ...
        );
}

Note: String.fromEnvironment('YOUR_KEY') is a dart function to get environment variables.
You can pass them to the running app using the command line.

flutter run --dart-define=YOUR_KEY=YOUR_VALUE

prefer pushing key using String.fromEnvironment('APP_STORE_ID', defaultValue: 'com.example.myapp.dev') instead of hard coded values.

You can still use hard coded values but it's not recommanded as it expose your keys in your repository.

VS code users

If you use VS code you can add a launch config for each of your environments.
ApparenceKit provides an example in the .vscode/launch.json file.

Don't forget to add your environment variables in the launch config.

{
    "name": "Dev",
    "request": "launch",
    "type": "dart",
    "args": [
        "--dart-define=BACKEND_URL=https://..",
        "--dart-define=ENV=dev"
    ]
},

🔥 Setup firestore

Congratulations you have nothing more to do.
The firestore configuration is already done for you.


🌏 Setup your backend API

Setup your own backend url

For testing purpose we provide a fake backend.
It answers to every request with a 200 status code and a fake json response.

You can setup your backend url using a Dart environment variable.

flutter run \
--dart-define=BACKEND_URL=https://myserver \
--dart-define=ENV=dev

Using VSCode you can configure a launch config for each of your environments like this
(put this in your project .vscode/launch.json file)

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Dev",
            "request": "launch",
            "type": "dart",
            "args": [
                "--dart-define=BACKEND_URL=https://..",
                "--dart-define=ENV=dev"
            ]
        },
        {
            "name": "Staging",
            "request": "launch",
            "type": "dart",
            "args": [
                "--dart-define=BACKEND_URL=https://..",
                "--dart-define=ENV=staging"
            ]
        },
        {
            "name": "Prod",
            "request": "launch",
            "type": "dart",
            "args": [
                "--dart-define=BACKEND_URL=https://...",
                "--dart-define=ENV=prod"
            ]
        }
    ]
}

Here we are, you can now play with your app.

You can now go to the next step - Setup your backend API to setup your API authentication.


📱 Setup Flavors (optionnal)

Setting up flavors allows you to run multiple versions of your app on the same device.
But this is not mandatory.
It requires a bit of configuration and it's not always necessary.

If you are not familiar with Android / Xcode flavors, you can skip this part.

You can setup flavors for a custom package name for every environments.
Ex:

  • com.example.myapp.dev (for dev)
  • com.example.myapp.stagin (for staging)
  • com.example.myapp (for production)

This will prevent from publishing a dev environment to your production.
Also you can run both staging and production on a single device (great for testing).