Using Environment Variables for Flavor-Specific Settings in Flutter

In Flutter development, different build environments (development, staging, production) often require different settings such as API endpoints, app names, or Firebase project configurations. Environment variables offer a clean and maintainable way to manage these flavor-specific settings. By using environment variables, you can easily switch between different configurations without modifying the code directly.

What are Environment Variables?

Environment variables are key-value pairs set outside the application code. They can be accessed by the application during runtime. This allows you to configure your application differently based on the environment it’s running in.

Why Use Environment Variables?

  • Configuration Management: Centralized configuration settings for different environments.
  • Security: Securely store sensitive information like API keys outside the codebase.
  • Maintainability: Easy to switch between environments without code changes.
  • Flexibility: Adapt application behavior dynamically based on the environment.

How to Implement Environment Variables in Flutter

To implement environment variables in Flutter, you can use several packages. One of the most popular and straightforward options is the flutter_dotenv package.

Step 1: Add flutter_dotenv Dependency

First, add the flutter_dotenv package to your pubspec.yaml file:

dependencies:
  flutter_dotenv: ^5.2.0

Then, run flutter pub get to install the package.

Step 2: Create Environment Files

Create separate .env files for each environment (e.g., .env.development, .env.staging, .env.production). These files will contain your environment-specific variables.

Example .env.development file:

API_URL=https://development.example.com/api
APP_NAME=MyApp (Dev)

Example .env.staging file:

API_URL=https://staging.example.com/api
APP_NAME=MyApp (Staging)

Example .env.production file:

API_URL=https://example.com/api
APP_NAME=MyApp

Step 3: Load the Environment Variables

In your main.dart file, load the appropriate .env file before running the app.

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load(fileName: ".env.development"); // Load the .env file

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: dotenv.get('APP_NAME'),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(dotenv.get('APP_NAME')),
      ),
      body: Center(
        child: Text('API URL: ${dotenv.get('API_URL')}'),
      ),
    );
  }
}

Step 4: Configure Flavors

Configure flavors using command-line arguments when running the Flutter app.

Example commands:

# Run in development mode
flutter run --dart-define=FLAVOR=development

# Run in staging mode
flutter run --dart-define=FLAVOR=staging

# Run in production mode
flutter run --dart-define=FLAVOR=production

Step 5: Conditionally Load Environment Variables

Modify your main.dart file to conditionally load the correct .env file based on the flavor.

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'dart:io' show Platform;

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();

  String fileName = '.env.development'; // Default to development
  const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'development');

  switch (flavor) {
    case 'production':
      fileName = '.env.production';
      break;
    case 'staging':
      fileName = '.env.staging';
      break;
    default:
      fileName = '.env.development';
  }

  await dotenv.load(fileName: fileName);

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: dotenv.get('APP_NAME'),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(dotenv.get('APP_NAME')),
      ),
      body: Center(
        child: Text('API URL: ${dotenv.get('API_URL')}'),
      ),
    );
  }
}

Additional Tips

  • .gitignore: Add .env* to your .gitignore file to prevent committing sensitive information to your repository.
  • Type Safety: Create Dart classes to access environment variables in a type-safe manner.
  • Error Handling: Implement error handling to gracefully handle cases where environment variables are missing.

Example: Type-Safe Access

Create a Dart class to encapsulate the environment variables:

import 'package:flutter_dotenv/flutter_dotenv.dart';

class Env {
  static String get apiUrl => dotenv.get('API_URL');
  static String get appName => dotenv.get('APP_NAME');
}

// Usage:
Text('API URL: ${Env.apiUrl}')

Conclusion

Using environment variables for flavor-specific settings in Flutter enhances your application’s configurability, security, and maintainability. By following the steps outlined above, you can easily manage different configurations for your development, staging, and production environments, making your Flutter development workflow more efficient and robust. Leverage the flutter_dotenv package and command-line arguments to streamline your configuration management process.