(β)Log

Flutter: Store data based on Environment

Published on
Authors

When developing an application, it is essential to have a well-defined environment setup to facilitate efficient and reliable development, testing, and deployment processes. The concept of having multiple environments, such as development, staging, and production, is a best practice commonly referred to as environment management.

Creating a clear separation between development and production environments provides numerous benefits for application development. By not directly accessing the production API, we mitigate the risk of accidental changes or disruptions to critical systems. It also safeguards sensitive data, such as API Keys, Client IDs, and Client Secrets, preventing their exposure during the development stage. Employing this approach ensures a secure and controlled development environment while safeguarding production data and maintaining the integrity of the overall application ecosystem.

Here are some methods we can use:

Use a config file ⚙️

Through this method, we will create a file config.dart and hard-coding data to an enum variable. Btw on this example, I will use 2 environments, staging, and production.

/// Use an enum to define data based on the environment
enum Environment {
	staging(
		baseUrl: "https://api.staging.com",
		clientId: "69",
		clientSecret: "clientSecretStaging",
		userClientId: "99",
		userClientSecret: "userClientSecretStaging",
	),
	production(
		baseUrl: "https://api.production.com",
		clientId: "69",
		clientSecret: "clientSecretProduction",
		userClientId: "99",
		userClientSecret: "userClientSecretProduciton",
	);

	const Environment({
		required this.baseUrl,
		required this.clientId,
		required this.clientSecret,
		required this.userClientId,
		required this.userClientSecret,
	});

	final String baseUrl;
	final String clientId;
	final String clientSecret;
	final String userClientId;
	final String userClientSecret;
}

/// Set default env to staging
Environment environment = Environment.staging;

And after that, we need to rename and copy the file main.dart. We rename main.dart to main_stg.dart and main_prd.dart and after that, we need to add some code to handle the environment thing.

/// main_stg.dart
import `package:your_app/config.dart`;

void main(){

environment = Environment.staging;

....
}
/// main_prd.dart
import `package:your_app/config.dart`;

void main(){

environment = Environment.production;

....
}

And to consume the data we just need to call the environment variable

/// api_services.dart
import `package:your_app/config.dart`;

class APIServices {
	 final baseUrl = environment.baseUrl;
	 final clientId = environment.clientId;

....
}

To run the app, we need to define a specific file target because by default flutter will search the file main. dart.

flutter run -t lib/main_stg.dart # or
flutter run -t lib/main_prd.dart

We can add config.dart to .gitignore so that file will stay on our local and not be published on our online repository.

config.dart

Passing the data via command-line arguments 🎯

Flutter also supports passing data via argument at compile time. So we can define the data using the --dart-define flag.

flutter run --dart-define BASE_URL=https://api.production.com

And to get that data on the dart code, we can do:

const baseUrl = String.fromEnvironment('BASE_URL');

if(baseUrl.isEmpty){
   throw AssertionError("BASE_URL is not set");
}

For multiple keys, you can do this

flutter run \
	--dart-define BASE_URL=https://api.production.com \
	--dart-define CLIENT_ID=69 \
	--dart-define CLIENT_SECRET=clientSecretProduction \
	--dart-define USER_CLIENT_ID=99 \
	--dart-define USER_CLIENT_SECRET=userClientSecretProduciton

New in flutter > 3.7: --dart-define-from-file

Starting from Flutter version 3.7, we can define that as a JSON file and use the --dart-define-from-file flag.

flutter run --dart-define-from-file .env.stg.json

Then we can add all the keys inside the JSON file based on the environment

.env.stg.json

{
  "BASE_URL": "https://api.staging.com",
  "CLIENT_ID": "69",
  "CLIENT_SECRET": "clientSecretStaging",
  "USER_CLIENT_ID": "99",
  "USER_CLIENT_SECRET": "userClientSecretProduction"
}

.env.prd.json

{
  "BASE_URL": "https://api.production.com",
  "CLIENT_ID": "69",
  "CLIENT_SECRET": "clientSecretProduction",
  "USER_CLIENT_ID": "99",
  "USER_CLIENT_SECRET": "userClientSecretProduction"
}

Don't forget to add that's file to your .gitignore

.env.stg.json
.env.prd.json

Bonus 🎁

To make it easy when developing, we can add it to the launch configuration.

VSCode

.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "stg",
      "request": "launch",
      "type": "dart",
      "program": "lib/main.dart",
      "args": ["--dart-define-from-file", ".env.stg.json"]
    },
    {
      "name": "prd",
      "request": "launch",
      "type": "dart",
      "program": "lib/main.dart",
      "args": ["--dart-define-from-file", ".env.prd.json"]
    }
  ]
}

IntelliJ Idea

Staging image

Production image