diff --git a/content/7.vaahflutter/3.essentials/2.environments.md b/content/7.vaahflutter/3.essentials/2.environments.md index f12b2ae9..fc09202c 100644 --- a/content/7.vaahflutter/3.essentials/2.environments.md +++ b/content/7.vaahflutter/3.essentials/2.environments.md @@ -1,13 +1,12 @@ --- -title: Environments +title: Environments --- -::alert{type="warning" class="flex items-center p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300" role="alert"} -Dependencies: -None -:: +### Dynamic Environment Configuration Using JSON in Flutter Assets -## Running the app in different environments +Using JSON files for environment configurations in Flutter is a robust, flexible, and secure approach. It simplifies managing environment-specific settings, enhances security by avoiding hard-coded sensitive data, and offers advantages over using the `flutter_dotenv` package. + +### How to use - In the default environment ```bash @@ -15,450 +14,131 @@ flutter run OR -flutter run --dart-define="environment=default" +flutter run --dart-define="ENV_PATH=assets/env/default.json" ``` - In the development environment ```bash -flutter run --dart-define="environment=develop" +flutter run --dart-define="ENV_PATH=assets/env/develop.json" ``` - In the staging environment ```bash -flutter run --dart-define="environment=stage" +flutter run --dart-define="ENV_PATH=assets/env/staging.json" ``` - In the Production environment ```bash -flutter run --dart-define="environment=production" +flutter run --dart-define="ENV_PATH=assets/env/production.json" ``` -## About environment configuration - -### Properties - -Environment config contains below properties. - -| **Property Name** | **Description** | -| --- | --- | -| appTitle | Represents full name of app | -| appTitleShort | Represents short name of app | -| envType | Represents environment type (e.g. develop, staging, production, etc.) | -| version | Represents current version, in major.minor.patch format (e.g. 1.0.0) | -| build | Represents build number, generally date (yyyymmdd) + build of current day (e.g. 2023010101) | -| backendUrl | No use | -| apiUrl | Represents root endpoint of url | -| timeoutLimit | Represents timeout limit for requests (in milliseconds) | -| firebaseId | Represents firebase id of app | -| enableLocalLogs | Used for enabling/ disabling Local Logs | -| enableCloudLogs | Used for enabling/ disabling Cloud Logs | -| enableApiLogInterceptor | Used for enabling/ disabling API Request and Response Local logs | -| sentryConfig | Contains all essential values for sentry | -| pushNotificationsServiceType | Used to set the Push Notifications Service Type | -| oneSignalConfig | Used to set the One signal config **(needed/ works only when push notification service type is remote)** | -| internalNotificationsServiceType | Used to set the Internal Notifications Service Type | -| pusherConfig | Used to set the Pusher config **(needed/ works only when internal notification service type is pusher)** | -| showDebugPanel | Used for enabling/ disabling Debug Panel | -| debugPanelColor | Used for changing color of Debug Panel | - -#### Sentry Config (sentryConfig) - -check more [here](../directory_structure/vaahextendflutter/services/performance_monitoring.md) and [here](../directory_structure/vaahextendflutter/services/logging_library/logging_library.md) - -| **Property Name** | **Description** | -| --- | --- | -| dsn | Data Source Name (which is unique per project amd developer can obtain that by creating a new project in sentry) | -| enableAutoPerformanceTracing | if set to false nothing will be monitored | -| autoAppStart | if enabled will monitor cold and warm start up time | -| enableUserInteractionTracing | if enabled will monitor User Interaction | -| enableAssetsInstrumentation | if enabled will monitor Asset Performance | -| tracesSampleRate | will report uncaught errors as per rate is set, i.e. if it's 0.4 then 40% of all uncaught error will be reported | - -#### Push Notifications Service Type (pushNotificationsServiceType) - -Check [this](../directory_structure/vaahextendflutter/services/notification/push/notification.md). - -Possible values for `pushNotificationsServiceType` are - -| **Property Name** | **Description** | -| --- | --- | -| PushNotificationsServiceType.local | No additional configuration needed to push local notifications | -| PushNotificationsServiceType.remote | This will allow dev to push notifications via Remote service (devices which are not local will also be allowed using player id) | -| PushNotificationsServiceType.both | This will allow dev to push notifications via Local service (for local device notifications) and to push notifications via Remote service (devices which are not local will also be allowed | -| PushNotificationsServiceType.none | No additional configuration needed. Choosing this will disable Push Notifications | - -If you choose `PushNotificationsServiceType.remote` or `PushNotificationsServiceType.both` you need to add `oneSignalConfig` as well. - -```dart -oneSignalConfig: const OneSignalConfig(appId: ''), -``` +#### Example Configuration (`assets/env/develop.json`) -#### Internal Notifications Service Type (internalNotificationsServiceType) - -Check [this](../directory_structure/vaahextendflutter/services/notification/internal/notification.md). - -Possible values for `internalNotificationsServiceType` are - -| **Property Name** | **Description** | -| --- | --- | -| InternalNotificationsServiceType.firebase | This will enable internal notifications via firestore | -| InternalNotificationsServiceType.pusher | This will enable internal notifications via pusher | -| InternalNotificationsServiceType.custom | This will enable internal notifications via custom service | -| InternalNotificationsServiceType.none | This will disable internal notifications | - -If you choose `InternalNotificationsServiceType.firebase` you need to setup firestore and firebase app as well, check more details [here](../directory_structure/vaahextendflutter/services/notification/internal/services/firebase.md#integration). - -If you choose `InternalNotificationsServiceType.pusher` you need to setup pusher as well, check more details [here](../directory_structure/vaahextendflutter/services/notification/internal/services/pusher.md#integration). - -If you choose `InternalNotificationsServiceType.cutom` you need to write custom service code in [custom.dart](../directory_structure/vaahextendflutter/services/notification/internal/services/custom.md) - -### Example of environment config -```dart -final EnvironmentConfig developConfig = EnvironmentConfig( - appTitle: 'VaahFlutter', - appTitleShort: 'VaahFlutter', - envType: 'develop', - version: version, - build: build, - backendUrl: '', - apiUrl: '', - timeoutLimit: 20 * 1000, // 20 seconds - firebaseId: '', - sentryConfig: const SentryConfig( - dsn: '', - enableAutoPerformanceTracing: true, - autoAppStart: true, - enableUserInteractionTracing: true, - enableAssetsInstrumentation: true, - tracesSampleRate: 0.6, - ), - enableLocalLogs: true, - enableCloudLogs: true, - enableApiLogInterceptor: true, - showDebugPanel: true, - debugPanelColor: AppTheme.colors['black']!.withOpacity(0.7), -); +```json +{ + "app_title": "MyApp", + "env_type": "development", + "api_url": "https://api.dev.example.com", + "enable_local_logs": true + ... +} ``` -## How can I create a new config and new environment and use it for my app. - -- Let's say I already have one default config (developConfig) and I want to copy some of it's properties and want to override some properties. If I don't have any config I need to create one like [this](#example-of-environment-config). - -- Then I can pass different configurations in `_envConfigs` array in [Source Code](#source-code). - -```dart -Map _envConfigs = { - 'develop': developConfig, - 'stage': developConfig.copyWith( - envType: 'stage', - enableLocalLogs: false, - ), - 'production': developConfig.copyWith( - envType: 'production', - enableLocalLogs: false, - enableApiLogInterceptor: false, - showDebugPanel: false, - ), - 'my-env': developConfig.copyWith( - envType: 'none', - enableCloudLogs: false, - ), -}; -``` +#### Steps to Implement -And then developer can just use `flutter run` command with their environment name -```bash -flutter run --dart-define="environment=my-env" -``` +1. **Update `pubspec.yaml`**: Add paths to JSON files in the `assets` section and run `flutter pub get`. -## Source Code - -`env.dart` file - -```dart -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; - -import './app_theme.dart'; -import './services/logging_library/logging_library.dart'; - -// After changing any const you will need to restart the app (Hot-reload won't work). - -// Version and build -const String version = '1.0.0'; // version format 1.0.0 (major.minor.patch) -const String build = '2022030201'; // build no format 'YYYYMMDDNUMBER' - -final EnvironmentConfig defaultConfig = EnvironmentConfig( - appTitle: 'VaahFlutter', - appTitleShort: 'VaahFlutter', - envType: 'default', - version: version, - build: build, - backendUrl: '', - apiUrl: '', - timeoutLimit: 20 * 1000, // 20 seconds - enableLocalLogs: true, - enableCloudLogs: true, - enableApiLogInterceptor: true, - pushNotificationsServiceType: PushNotificationsServiceType.both, - oneSignalConfig: const OneSignalConfig(appId: ''), - internalNotificationsServiceType: InternalNotificationsServiceType.custom, - pusherConfig: const PusherConfig(apiKey: '', cluster: ''), - showDebugPanel: true, - debugPanelColor: AppTheme.colors['black']!.withOpacity(0.8), -); - -// To add new configuration add new key, value pair in envConfigs -Map _envConfigs = { - // Do not remove default config - 'default': defaultConfig.copyWith( - envType: 'default', - ), - 'develop': defaultConfig.copyWith( - envType: 'develop', - enableCloudLogs: false, - ), - 'stage': defaultConfig.copyWith( - envType: 'stage', - enableCloudLogs: true, - ), - 'production': defaultConfig.copyWith( - envType: 'production', - enableLocalLogs: false, - enableApiLogInterceptor: false, - showDebugPanel: false, - ), -}; - -class EnvController extends GetxController { - final GetStorage _storage = GetStorage(); - late EnvironmentConfig _config; - - EnvironmentConfig get config => _config; - - EnvController(String environment) { - try { - _config = getSpecificConfig(environment).copyWith(openCount: _storage.read('open_count')); - } catch (error, stackTrace) { - Log.exception(error, stackTrace: stackTrace); - exit(0); - } - } - - EnvironmentConfig getSpecificConfig(String key) { - bool configExists = _envConfigs.containsKey(key); - if (configExists) { - return _envConfigs[key]!; - } - throw Exception('Environment configuration not found for key: $key'); - } - - Future increaseOpenCount() async { - await _storage.write('open_count', _config.openCount + 1); - _config = _config.copyWith(openCount: _config.openCount + 1); - } -} + ```yaml + flutter: + assets: + - assets/env/develop.json + - assets/env/production.json + - assets/env/staging.json + - assets/env/default.json + ``` -class EnvironmentConfig { - final String appTitle; - final String appTitleShort; - final String envType; - final String version; - final String build; - final int openCount; - final String backendUrl; - final String apiUrl; - final String? firebaseId; - final int timeoutLimit; - final bool enableLocalLogs; - final bool enableCloudLogs; - final SentryConfig? sentryConfig; - final bool enableApiLogInterceptor; - final PushNotificationsServiceType pushNotificationsServiceType; - final InternalNotificationsServiceType internalNotificationsServiceType; - final OneSignalConfig? oneSignalConfig; - final PusherConfig? pusherConfig; - final bool showDebugPanel; - final Color debugPanelColor; - - const EnvironmentConfig({ - required this.appTitle, - required this.appTitleShort, - required this.envType, - required this.version, - required this.build, - this.openCount = 0, - required this.backendUrl, - required this.apiUrl, - this.firebaseId, - required this.timeoutLimit, - required this.enableLocalLogs, - required this.enableCloudLogs, - this.sentryConfig, - required this.enableApiLogInterceptor, - required this.pushNotificationsServiceType, - required this.internalNotificationsServiceType, - this.oneSignalConfig, - this.pusherConfig, - required this.showDebugPanel, - required this.debugPanelColor, - }); - - static EnvironmentConfig getEnvConfig() { - final bool envControllerExists = Get.isRegistered(); - if (!envControllerExists) { - setEnvConfig(); - } - EnvController envController = Get.find(); - return envController.config; - } - - static void setEnvConfig() { - String environment = const String.fromEnvironment('environment', defaultValue: 'default'); - final EnvController envController = Get.put(EnvController(environment)); - Log.info( - 'Env Type: ${envController.config.envType}', - disableCloudLogging: true, - ); - Log.info( - 'Version: ${envController.config.version}+${envController.config.build}', - disableCloudLogging: true, - ); - } - - EnvironmentConfig copyWith({ - String? appTitle, - String? appTitleShort, - String? envType, - String? version, - String? build, - int? openCount, - String? backendUrl, - String? apiUrl, - String? firebaseId, - int? timeoutLimit, - bool? enableLocalLogs, - bool? enableCloudLogs, - SentryConfig? sentryConfig, - bool? enableApiLogInterceptor, - PushNotificationsServiceType? pushNotificationsServiceType, - InternalNotificationsServiceType? internalNotificationsServiceType, - OneSignalConfig? oneSignalConfig, - PusherConfig? pusherConfig, - bool? showDebugPanel, - Color? debugPanelColor, - }) { - return EnvironmentConfig( - appTitle: appTitle ?? this.appTitle, - appTitleShort: appTitleShort ?? this.appTitleShort, - envType: envType ?? this.envType, - version: version ?? this.version, - build: build ?? this.build, - openCount: openCount ?? this.openCount, - backendUrl: backendUrl ?? this.backendUrl, - apiUrl: apiUrl ?? this.apiUrl, - firebaseId: firebaseId ?? this.firebaseId, - timeoutLimit: timeoutLimit ?? this.timeoutLimit, - enableLocalLogs: enableLocalLogs ?? this.enableLocalLogs, - enableCloudLogs: enableCloudLogs ?? this.enableCloudLogs, - sentryConfig: sentryConfig ?? this.sentryConfig, - enableApiLogInterceptor: enableApiLogInterceptor ?? this.enableApiLogInterceptor, - pushNotificationsServiceType: - pushNotificationsServiceType ?? this.pushNotificationsServiceType, - internalNotificationsServiceType: - internalNotificationsServiceType ?? this.internalNotificationsServiceType, - oneSignalConfig: oneSignalConfig ?? this.oneSignalConfig, - pusherConfig: pusherConfig ?? this.pusherConfig, - showDebugPanel: showDebugPanel ?? this.showDebugPanel, - debugPanelColor: debugPanelColor ?? this.debugPanelColor, - ); - } - - Future increaseOpenCount() async { - final bool envControllerExists = Get.isRegistered(); - if (!envControllerExists) throw Exception('No EnvController Is Registered'); - await Get.find().increaseOpenCount(); - } -} -enum PushNotificationsServiceType { local, remote, both, none } - -enum InternalNotificationsServiceType { pusher, firebase, custom, none } - -class SentryConfig { - final String dsn; - final bool autoAppStart; // To record cold and warm start up time - final double tracesSampleRate; - final bool enableAutoPerformanceTracing; - final bool enableUserInteractionTracing; - final bool enableAssetsInstrumentation; - - const SentryConfig({ - required this.dsn, - this.autoAppStart = true, - this.tracesSampleRate = 0.6, - this.enableAutoPerformanceTracing = true, - this.enableUserInteractionTracing = true, - this.enableAssetsInstrumentation = true, - }); - - SentryConfig copyWith({ - String? dsn, - bool? autoAppStart, - double? tracesSampleRate, - bool? enableAutoPerformanceTracing, - bool? enableUserInteractionTracing, - bool? enableAssetsInstrumentation, - }) { - return SentryConfig( - dsn: dsn ?? this.dsn, - autoAppStart: autoAppStart ?? this.autoAppStart, - tracesSampleRate: tracesSampleRate ?? this.tracesSampleRate, - enableAutoPerformanceTracing: - enableAutoPerformanceTracing ?? this.enableAutoPerformanceTracing, - enableUserInteractionTracing: - enableUserInteractionTracing ?? this.enableUserInteractionTracing, - enableAssetsInstrumentation: enableAssetsInstrumentation ?? this.enableAssetsInstrumentation, - ); - } -} -class OneSignalConfig { - final String appId; +2. **Fetch Configuration**: We have used `String.fromEnvironment` in Dart to load the JSON file specified by `ENV_PATH`. - const OneSignalConfig({ - required this.appId, - }); + ```dart + const String envPath = String.fromEnvironment("ENV_PATH", defaultValue: "assets/env/default.json"); + ``` - OneSignalConfig copyWith({ - String? appId, - }) { - return OneSignalConfig( - appId: appId ?? this.appId, - ); - } -} +3. **Run the App**: Use `flutter run` with the `--dart-define` flag to specify the environment configuration file. -class PusherConfig { - final String apiKey; - final String cluster; - - const PusherConfig({ - required this.apiKey, - required this.cluster, - }); - - PusherConfig copyWith({ - String? apiKey, - String? cluster, - }) { - return PusherConfig( - apiKey: apiKey ?? this.apiKey, - cluster: cluster ?? this.cluster, - ); - } -} -``` +### Why Use JSON Files for Environment Configuration + +#### Benefits of Our JSON Approach + +1. **Simplicity and Readability** + - JSON files are easy to read and write, making configuration management straightforward. + +2. **Consistency** + - Uniform settings across different environments (development, staging, production). + +3. **Flexibility** + - Easily switch between environments without changing the codebase. + +4. **Separation of Concerns** + - Keeps environment-specific settings separate from the main application logic. + +5. **Environment-Specific Config** + - Tailor settings for different environments (development, testing, production) without code changes. + +6. **The Keeper of Secrets** + - Stores sensitive information like API keys, passwords, and configuration settings securely. + +#### How It Is Better Than the Flutter Dotenv Package + +1. **Structure and Type Safety** + - JSON files provide a structured way to store configuration data. + - Enforce type safety with Dart's JSON serialization. + +2. **Integration with Asset Management** + - JSON files are managed as Flutter assets, ensuring correct bundling. + - Use Flutter's `dart-define` feature to specify the configuration file at runtime. + +3. **Ease of Use** + - JSON is a universally known format, making it easier for teams to adopt. + +### Security Considerations + +#### Handling Sensitive Information + +1. **Environment Separation** + - Prevents hard-coding sensitive data, reducing the risk of exposure. + +2. **Gitignore Sensitive Files** + - Add `assets/env/*.json` to `.gitignore` to keep sensitive files out of version control, preventing accidental exposure in public repositories. + + ```gitignore + assets/env/*.json + ``` + +### Maintaining Consistency with Environment Variables + +1. **Centralized Configuration** + - Centralizes all environment-specific settings in JSON files, ensuring consistency across different environments. + +2. **Ease of Updates** + - Allows for quick updates to environment-specific settings. + +3. **Minimizes Errors** + - Reduces the likelihood of misconfigurations by keeping all environment variables in a single, structured file. + +4. **Developer-Friendly** + - Simplifies onboarding for new developers by providing clear, consistent environment settings. + +5. **CI/CD pipelines** + - Automatically switch configurations based on the environment in CI/CD pipelines. + +### Addressing Local Development Challenges + +1. **Local Development Setup** + - Provide developers with default and development JSON configuration files to simplify setup. + +2. **Ease of Testing** + - Allow developers to easily switch between different environment configurations for testing. + +For more details, [visit this link](../5.directory_structure/3.vaahextendflutter/2.env.md). \ No newline at end of file diff --git a/content/7.vaahflutter/5.directory_structure/3.vaahextendflutter/2.env.md b/content/7.vaahflutter/5.directory_structure/3.vaahextendflutter/2.env.md index e7669b46..8e8bc097 100644 --- a/content/7.vaahflutter/5.directory_structure/3.vaahextendflutter/2.env.md +++ b/content/7.vaahflutter/5.directory_structure/3.vaahextendflutter/2.env.md @@ -17,290 +17,124 @@ flutter run OR -flutter run --dart-define="environment=default" +flutter run --dart-define="ENV_PATH=assets/env/default.json" ``` - In the development environment ```bash -flutter run --dart-define="environment=develop" +flutter run --dart-define="ENV_PATH=assets/env/develop.json" ``` - In the staging environment ```bash -flutter run --dart-define="environment=stage" +flutter run --dart-define="ENV_PATH=assets/env/staging.json" ``` - In the Production environment ```bash -flutter run --dart-define="environment=production" +flutter run --dart-define="ENV_PATH=assets/env/production.json" ``` -## About environment configuration - -### Properties - -Environment config contains below properties. - -| **Property Name** | **Description** | -| --- | --- | -| appTitle | Represents full name of app | -| appTitleShort | Represents short name of app | -| envType | Represents environment type (e.g. develop, staging, production, etc.) | -| version | Represents current version, in major.minor.patch format (e.g. 1.0.0) | -| build | Represents build number, generally date (yyyymmdd) + build of current day (e.g. 2023010101) | -| backendUrl | No use | -| apiUrl | Represents root endpoint of url | -| timeoutLimit | Represents timeout limit for requests (in milliseconds) | -| firebaseId | Represents firebase id of app | -| enableLocalLogs | Used for enabling/ disabling Local Logs | -| enableCloudLogs | Used for enabling/ disabling Cloud Logs | -| enableApiLogInterceptor | Used for enabling/ disabling API Request and Response Local logs | -| sentryConfig | Contains all essential values for sentry | -| pushNotificationsServiceType | Used to set the Push Notifications Service Type | -| oneSignalConfig | Used to set the One signal config **(needed/ works only when push notification service type is remote)** | -| internalNotificationsServiceType | Used to set the Internal Notifications Service Type | -| pusherConfig | Used to set the Pusher config **(needed/ works only when internal notification service type is pusher)** | -| showDebugPanel | Used for enabling/ disabling Debug Panel | -| debugPanelColor | Used for changing color of Debug Panel | - -#### Sentry Config (sentryConfig) - -check more [here](../directory_structure/vaahextendflutter/services/performance_monitoring.md) and [here](../directory_structure/vaahextendflutter/services/logging_library/logging_library.md) - -| **Property Name** | **Description** | -| --- | --- | -| dsn | Data Source Name (which is unique per project amd developer can obtain that by creating a new project in sentry) | -| enableAutoPerformanceTracing | if set to false nothing will be monitored | -| autoAppStart | if enabled will monitor cold and warm start up time | -| enableUserInteractionTracing | if enabled will monitor User Interaction | -| enableAssetsInstrumentation | if enabled will monitor Asset Performance | -| tracesSampleRate | will report uncaught errors as per rate is set, i.e. if it's 0.4 then 40% of all uncaught error will be reported | - -#### Push Notifications Service Type (pushNotificationsServiceType) - -Check [this](../directory_structure/vaahextendflutter/services/notification/push/notification.md). - -Possible values for `pushNotificationsServiceType` are - -| **Property Name** | **Description** | -| --- | --- | -| PushNotificationsServiceType.local | No additional configuration needed to push local notifications | -| PushNotificationsServiceType.remote | This will allow dev to push notifications via Remote service (devices which are not local will also be allowed using player id) | -| PushNotificationsServiceType.both | This will allow dev to push notifications via Local service (for local device notifications) and to push notifications via Remote service (devices which are not local will also be allowed | -| PushNotificationsServiceType.none | No additional configuration needed. Choosing this will disable Push Notifications | - -If you choose `PushNotificationsServiceType.remote` or `PushNotificationsServiceType.both` you need to add `oneSignalConfig` as well. -```dart -oneSignalConfig: const OneSignalConfig(appId: ''), -``` - -#### Internal Notifications Service Type (internalNotificationsServiceType) - -Check [this](../directory_structure/vaahextendflutter/services/notification/internal/notification.md). - -Possible values for `internalNotificationsServiceType` are - -| **Property Name** | **Description** | -| --- | --- | -| InternalNotificationsServiceType.firebase | This will enable internal notifications via firestore | -| InternalNotificationsServiceType.pusher | This will enable internal notifications via pusher | -| InternalNotificationsServiceType.custom | This will enable internal notifications via custom service | -| InternalNotificationsServiceType.none | This will disable internal notifications | -If you choose `InternalNotificationsServiceType.firebase` you need to setup firestore and firebase app as well, check more details [here](../directory_structure/vaahextendflutter/services/notification/internal/services/firebase.md#integration). -If you choose `InternalNotificationsServiceType.pusher` you need to setup pusher as well, check more details [here](../directory_structure/vaahextendflutter/services/notification/internal/services/pusher.md#integration). - -If you choose `InternalNotificationsServiceType.cutom` you need to write custom service code in [custom.dart](../directory_structure/vaahextendflutter/services/notification/internal/services/custom.md) - -### Example of environment config -```dart -final EnvironmentConfig developConfig = EnvironmentConfig( - appTitle: 'VaahFlutter', - appTitleShort: 'VaahFlutter', - envType: 'develop', - version: version, - build: build, - backendUrl: '', - apiUrl: '', - timeoutLimit: 20 * 1000, // 20 seconds - firebaseId: '', - sentryConfig: const SentryConfig( - dsn: '', - enableAutoPerformanceTracing: true, - autoAppStart: true, - enableUserInteractionTracing: true, - enableAssetsInstrumentation: true, - tracesSampleRate: 0.6, - ), - enableLocalLogs: true, - enableCloudLogs: true, - enableApiLogInterceptor: true, - showDebugPanel: true, - debugPanelColor: AppTheme.colors['black']!.withOpacity(0.7), -); +#### Example: `assets/env/develop.json` +```json +{ + "app_title": "VaahFlutter", + "app_title_short": "VaahFlutter", + "env_type": "Develop", + "version": "1.0.0", + "build": "1", + "backend_url": "", + "api_url": "", + "firebase_id": null, + "timeout_limit": 20, + "enable_local_logs": true, + "enable_cloud_logs": false, + "enable_api_log_interceptor": false, + "sentry_config": null, + "push_notifications_service_type": "none", + "internal_notifications_service_type": "none", + "one_signal_config": null, + "pusher_config": null, + "show_debug_panel": true, + "debug_panel_color": 3422552064 +} ``` -## How can I create a new config and new environment and use it for my app. - -- Let's say I already have one default config (developConfig) and I want to copy some of it's properties and want to override some properties. If I don't have any config I need to create one like [this](#example-of-environment-config). +### Step 2: Update `pubspec.yaml` +Next, update the `pubspec.yaml` file to include the paths to these JSON files in the `assets` section. This step is crucial because Flutter needs to know about the assets that should be bundled with the application. -- Then I can pass different configurations in `_envConfigs` array in [Source Code](#source-code). - -```dart -Map _envConfigs = { - 'develop': developConfig, - 'stage': developConfig.copyWith( - envType: 'stage', - enableLocalLogs: false, - ), - 'production': developConfig.copyWith( - envType: 'production', - enableLocalLogs: false, - enableApiLogInterceptor: false, - showDebugPanel: false, - ), - 'my-env': developConfig.copyWith( - envType: 'none', - enableCloudLogs: false, - ), -}; -``` - -And then developer can just use `flutter run` command with their environment name -```bash -flutter run --dart-define="environment=my-env" +```yaml +flutter: + assets: + - assets/env/develop.json + - assets/env/staging.json + - assets/env/production.json ``` -## Source Code +### Step 3: Fetch Configuration from JSON Files +To fetch the configuration settings from the specified JSON file, we have used the `String.fromEnvironment` method in Dart. This method allows us to pass environment variables to your Flutter application at runtime. -`env.dart` file +#### Source Code ```dart +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; - -import './app_theme.dart'; -import './services/logging_library/logging_library.dart'; - -// After changing any const you will need to restart the app (Hot-reload won't work). - -// Version and build -const String version = '1.0.0'; // version format 1.0.0 (major.minor.patch) -const String build = '2022030201'; // build no format 'YYYYMMDDNUMBER' - -final EnvironmentConfig defaultConfig = EnvironmentConfig( - appTitle: 'VaahFlutter', - appTitleShort: 'VaahFlutter', - envType: 'default', - version: version, - build: build, - backendUrl: '', - apiUrl: '', - timeoutLimit: 20 * 1000, // 20 seconds - enableLocalLogs: true, - enableCloudLogs: true, - enableApiLogInterceptor: true, - pushNotificationsServiceType: PushNotificationsServiceType.both, - oneSignalConfig: const OneSignalConfig(appId: ''), - internalNotificationsServiceType: InternalNotificationsServiceType.custom, - pusherConfig: const PusherConfig(apiKey: '', cluster: ''), - showDebugPanel: true, - debugPanelColor: AppTheme.colors['black']!.withOpacity(0.8), -); - -// To add new configuration add new key, value pair in envConfigs -Map _envConfigs = { - // Do not remove default config - 'default': defaultConfig.copyWith( - envType: 'default', - ), - 'develop': defaultConfig.copyWith( - envType: 'develop', - enableCloudLogs: false, - ), - 'stage': defaultConfig.copyWith( - envType: 'stage', - enableCloudLogs: true, - ), - 'production': defaultConfig.copyWith( - envType: 'production', - enableLocalLogs: false, - enableApiLogInterceptor: false, - showDebugPanel: false, - ), -}; +import 'package:json_annotation/json_annotation.dart'; -class EnvController extends GetxController { - final GetStorage _storage = GetStorage(); - late EnvironmentConfig _config; +import '../services/logging_library/logging_library.dart'; +import 'logging.dart'; +import 'notification.dart'; +part 'env.g.dart'; + +class EnvController extends GetxController { + EnvironmentConfig _config = EnvironmentConfig.defaultConfig(); EnvironmentConfig get config => _config; - EnvController(String environment) { + Future initialize() async { try { - _config = getSpecificConfig(environment).copyWith(openCount: _storage.read('open_count')); + const String envPath = String.fromEnvironment("ENV_PATH"); + if (envPath.isEmpty) { + Log.warning("INVALID ENVIRONMENT PATH"); + return; + } + Log.success("ENVIRONMENT PATH: $envPath"); + final String jsonConfig = await rootBundle.loadString(envPath); + if (jsonConfig.isNotEmpty) { + final Map json = jsonDecode(jsonConfig); + _config = EnvironmentConfig.fromJson(json); + } else { + throw Exception('Environment configuration not found for key: $envPath'); + } } catch (error, stackTrace) { Log.exception(error, stackTrace: stackTrace); exit(0); } } - - EnvironmentConfig getSpecificConfig(String key) { - bool configExists = _envConfigs.containsKey(key); - if (configExists) { - return _envConfigs[key]!; - } - throw Exception('Environment configuration not found for key: $key'); - } - - Future increaseOpenCount() async { - await _storage.write('open_count', _config.openCount + 1); - _config = _config.copyWith(openCount: _config.openCount + 1); - } } +@JsonSerializable(fieldRename: FieldRename.snake) class EnvironmentConfig { - final String appTitle; - final String appTitleShort; - final String envType; - final String version; - final String build; - final int openCount; - final String backendUrl; - final String apiUrl; - final String? firebaseId; - final int timeoutLimit; - final bool enableLocalLogs; - final bool enableCloudLogs; - final SentryConfig? sentryConfig; - final bool enableApiLogInterceptor; - final PushNotificationsServiceType pushNotificationsServiceType; - final InternalNotificationsServiceType internalNotificationsServiceType; - final OneSignalConfig? oneSignalConfig; - final PusherConfig? pusherConfig; - final bool showDebugPanel; - final Color debugPanelColor; - const EnvironmentConfig({ required this.appTitle, required this.appTitleShort, required this.envType, required this.version, required this.build, - this.openCount = 0, - required this.backendUrl, required this.apiUrl, this.firebaseId, required this.timeoutLimit, required this.enableLocalLogs, required this.enableCloudLogs, - this.sentryConfig, required this.enableApiLogInterceptor, + this.sentryConfig, required this.pushNotificationsServiceType, required this.internalNotificationsServiceType, this.oneSignalConfig, @@ -309,158 +143,91 @@ class EnvironmentConfig { required this.debugPanelColor, }); - static EnvironmentConfig getEnvConfig() { - final bool envControllerExists = Get.isRegistered(); - if (!envControllerExists) { - setEnvConfig(); - } - EnvController envController = Get.find(); - return envController.config; - } + final String appTitle; + final String appTitleShort; + final String envType; + final String version; + final String build; + final String apiUrl; + final String? firebaseId; + final int timeoutLimit; + final bool enableLocalLogs; + final bool enableCloudLogs; + final bool enableApiLogInterceptor; + final SentryConfig? sentryConfig; + final PushNotificationsServiceType pushNotificationsServiceType; + final InternalNotificationsServiceType internalNotificationsServiceType; + final OneSignalConfig? oneSignalConfig; + final PusherConfig? pusherConfig; + final bool showDebugPanel; + @JsonKey(fromJson: _colorFromJson, toJson: _colorToJson) + final Color debugPanelColor; - static void setEnvConfig() { - String environment = const String.fromEnvironment('environment', defaultValue: 'default'); - final EnvController envController = Get.put(EnvController(environment)); - Log.info( - 'Env Type: ${envController.config.envType}', - disableCloudLogging: true, - ); - Log.info( - 'Version: ${envController.config.version}+${envController.config.build}', - disableCloudLogging: true, - ); + static Color _colorFromJson(int color) { + return Color(color); } - EnvironmentConfig copyWith({ - String? appTitle, - String? appTitleShort, - String? envType, - String? version, - String? build, - int? openCount, - String? backendUrl, - String? apiUrl, - String? firebaseId, - int? timeoutLimit, - bool? enableLocalLogs, - bool? enableCloudLogs, - SentryConfig? sentryConfig, - bool? enableApiLogInterceptor, - PushNotificationsServiceType? pushNotificationsServiceType, - InternalNotificationsServiceType? internalNotificationsServiceType, - OneSignalConfig? oneSignalConfig, - PusherConfig? pusherConfig, - bool? showDebugPanel, - Color? debugPanelColor, - }) { - return EnvironmentConfig( - appTitle: appTitle ?? this.appTitle, - appTitleShort: appTitleShort ?? this.appTitleShort, - envType: envType ?? this.envType, - version: version ?? this.version, - build: build ?? this.build, - openCount: openCount ?? this.openCount, - backendUrl: backendUrl ?? this.backendUrl, - apiUrl: apiUrl ?? this.apiUrl, - firebaseId: firebaseId ?? this.firebaseId, - timeoutLimit: timeoutLimit ?? this.timeoutLimit, - enableLocalLogs: enableLocalLogs ?? this.enableLocalLogs, - enableCloudLogs: enableCloudLogs ?? this.enableCloudLogs, - sentryConfig: sentryConfig ?? this.sentryConfig, - enableApiLogInterceptor: enableApiLogInterceptor ?? this.enableApiLogInterceptor, - pushNotificationsServiceType: - pushNotificationsServiceType ?? this.pushNotificationsServiceType, - internalNotificationsServiceType: - internalNotificationsServiceType ?? this.internalNotificationsServiceType, - oneSignalConfig: oneSignalConfig ?? this.oneSignalConfig, - pusherConfig: pusherConfig ?? this.pusherConfig, - showDebugPanel: showDebugPanel ?? this.showDebugPanel, - debugPanelColor: debugPanelColor ?? this.debugPanelColor, - ); + static int _colorToJson(Color color) { + return color.value; } - Future increaseOpenCount() async { - final bool envControllerExists = Get.isRegistered(); - if (!envControllerExists) throw Exception('No EnvController Is Registered'); - await Get.find().increaseOpenCount(); - } -} + factory EnvironmentConfig.fromJson(Map json) => + _$EnvironmentConfigFromJson(json); -enum PushNotificationsServiceType { local, remote, both, none } - -enum InternalNotificationsServiceType { pusher, firebase, custom, none } - -class SentryConfig { - final String dsn; - final bool autoAppStart; // To record cold and warm start up time - final double tracesSampleRate; - final bool enableAutoPerformanceTracing; - final bool enableUserInteractionTracing; - final bool enableAssetsInstrumentation; - - const SentryConfig({ - required this.dsn, - this.autoAppStart = true, - this.tracesSampleRate = 0.6, - this.enableAutoPerformanceTracing = true, - this.enableUserInteractionTracing = true, - this.enableAssetsInstrumentation = true, - }); + Map toJson() => _$EnvironmentConfigToJson(this); - SentryConfig copyWith({ - String? dsn, - bool? autoAppStart, - double? tracesSampleRate, - bool? enableAutoPerformanceTracing, - bool? enableUserInteractionTracing, - bool? enableAssetsInstrumentation, - }) { - return SentryConfig( - dsn: dsn ?? this.dsn, - autoAppStart: autoAppStart ?? this.autoAppStart, - tracesSampleRate: tracesSampleRate ?? this.tracesSampleRate, - enableAutoPerformanceTracing: - enableAutoPerformanceTracing ?? this.enableAutoPerformanceTracing, - enableUserInteractionTracing: - enableUserInteractionTracing ?? this.enableUserInteractionTracing, - enableAssetsInstrumentation: enableAssetsInstrumentation ?? this.enableAssetsInstrumentation, - ); + static EnvironmentConfig get getConfig { + final bool isRegistered = Get.isRegistered(); + if (isRegistered) { + EnvController envController = Get.find(); + return envController.config; + } else { + return EnvironmentConfig.defaultConfig(); + } } -} -class OneSignalConfig { - final String appId; - - const OneSignalConfig({ - required this.appId, - }); - - OneSignalConfig copyWith({ - String? appId, - }) { - return OneSignalConfig( - appId: appId ?? this.appId, + factory EnvironmentConfig.defaultConfig() { + return EnvironmentConfig( + appTitle: 'VaahFlutter', + appTitleShort: 'VaahFlutter', + envType: 'default', + version: '1.0.0', + build: '1', + apiUrl: '', + timeoutLimit: 20, // 20 seconds + enableLocalLogs: true, + enableCloudLogs: false, + enableApiLogInterceptor: false, + pushNotificationsServiceType: PushNotificationsServiceType.none, + internalNotificationsServiceType: InternalNotificationsServiceType.none, + showDebugPanel: true, + debugPanelColor: Colors.black.withOpacity(0.8), ); } } -class PusherConfig { - final String apiKey; - final String cluster; +// Example +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + final EnvironmentConfig config = EnvironmentConfig.getConfig; - const PusherConfig({ - required this.apiKey, - required this.cluster, - }); - - PusherConfig copyWith({ - String? apiKey, - String? cluster, - }) { - return PusherConfig( - apiKey: apiKey ?? this.apiKey, - cluster: cluster ?? this.cluster, + return MaterialApp( + title: config.appTitle, + home: HomeScreen(), ); } } ``` + +### Step 4: Running the Application with Environment Config +To run the Flutter application with a specific environment configuration, use the `flutter run` command with the `--dart-define` flag. This flag allows you to define the environment variable `ENV_PATH` at runtime. + +```sh +flutter run --dart-define="ENV_PATH=assets/env/develop.json" +``` + +Replace `develop.json` with the appropriate JSON file for the environment you wish to target (e.g., `staging.json`, `production.json`, `test.json`). + + +By following these steps, you can easily manage different configurations for various environments in your Flutter application. This approach uses JSON files to store environment-specific settings and leverages the power of Flutter's asset management and `dart-define` feature to switch configurations at runtime. \ No newline at end of file