Categories
Flutter

Environment Configuration In Flutter App

In this post, we will go over how we can configure development, staging and production environment configuration in Flutter app. We will create different environment configurations and load the right environment during runtime.

Introduction To Environment Configuration

In any app development process, we need to create different environments to run the app in.

For example, if you are developing against your local environment and connecting with API server on local machine, then your api url needs to point to localhost.

Similarly, if you are testing the app in a staging environment which is a replica of production environment, then your app needs to use urls for the staging endpoints. Finally, when you want to release the app, it should be using the production endpoints.

Environment Configuration In Flutter App

Not only the API endpoints, any app also needs to consider these environment variables for other purposes as well.

You might want to track various events for analytics purposes in the app; but only in the production environment. You might need to display mobile ads in the production environment. Or you might want to send error reports in the Staging and Production environment only.

So, how can we configure development, staging and production environments in Flutter app?

Define App Environments

We start by defining an abstract class BaseConfig which will hold our app environment variables.

Define BaseConfig Environment

abstract class BaseConfig {
  String get apiHost;
  bool get useHttps;
  bool get trackEvents;
  bool get reportErrors;
}

The BaseConfig class has various fields:

  • apiHost represents API Endpoint host server
  • useHttps checks to use HTTPS when making API calls
  • trackEvents checks to track user activities like Firebase events in app
  • reportErrors represents whether to report runtime errors in app or not

We can have environment variables and fields as needed in the app here.

Next, we need implement the BaseConfig class for each environment.

Configuration For Development Environment

The development environment configuration could look like this:

class DevConfig implements BaseConfig {
  String get apiHost => "localhost";

  bool get reportErrors => false;

  bool get trackEvents => false;

  bool get useHttps => false;
}

Configuration For Staging Environment

Similarly, the staging environment configuration could look like this:

class StagingConfig implements BaseConfig {
  String get apiHost => "staging.example.com";

  bool get reportErrors => true;

  bool get trackEvents => false;

  bool get useHttps => true;
}

Configuration For Production Environment

Finally, the production environment configuration:

class ProdConfig implements BaseConfig {
  String get apiHost => "example.com";

  bool get reportErrors => true;

  bool get trackEvents => true;

  bool get useHttps => true;
}

We have now defined classes for development, staging and production environments in Flutter app. Next, we need to make sure our app uses these environments properly.

Load Environment Configuration In Flutter App

In order to load proper environment configuration in our Flutter app, we will create a class called Environment which can set configuration dynamically.

class Environment {
  factory Environment() {
    return _singleton;
  }

  Environment._internal();

  static final Environment _singleton = Environment._internal();

  static const String DEV = 'DEV';
  static const String STAGING = 'STAGING';
  static const String PROD = 'PROD';

  BaseConfig config;

  initConfig(String environment) {
    config = _getConfig(environment);
  }

  BaseConfig _getConfig(String environment) {
    switch (environment) {
      case Environment.PROD:
        return ProdConfig();
      case Environment.STAGING:
        return StagingConfig();
      default:
        return DevConfig();
    }
  }
}

Since the Environment class has a singleton implementation, this ensures that the environment configuration doesn’t change through out the application lifecycle.

Initialize Environment Configuration

Now, all that’s left to do is initialize the environment configuration in our app. We should do this at the main entry point for the app i.e. inside the main.dart main() function.

void main() {
  const String environment = String.fromEnvironment(
    'ENVIRONMENT',
    defaultValue: Environment.DEV,
  );

  Environment().initConfig(environment);

  runApp(MyApp());
}

We have modified the main method to initialize the app environment during runtime. The main method expects a argument named ENVIRONMENT whose default value is DEV.

So, by default the app runs in development environment if no other value is specified.

Get Environment Variable Value When Needed In App

Once the environment is initialized, we can make use of the config values anywhere inside the app by doing something like this:

// somewhere inside your app where you are make HTTP call

final String apiHost = Environment().config.apiHost;
final bool useHttps = Environment().config.useHttps;

If you have been paying attention until now, I am sure you are wondering, “Everything’s looking great, but how do I pass the argument to the main method?

Well I will leave that to you to figure out for now 😛

Just kidding!

Starting with Flutter 1.17, Flutter supports receiving additional arguments to the main function using the --dart-define keywords.

So, we can use this to tie things up for configuring development, staging and production environments in Flutter.

Run the app in staging mode:

flutter run --dart-define=ENVIRONMENT=STAGING

Run the app in production mode:

flutter run --dart-define=ENVIRONMENT=STAGING

Or run it in development mode:

flutter run --dart-define=ENVIRONMENT=STAGING
//or simply
flutter run

You can setup these arguments in your Makefile or also in your CI/CD pipelines as well.

Configure Gitlab To Deploy Flutter App Via CodeMagic