Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,6 @@ app.*.symbols
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
/app/env/


/modules/common/.flutter-plugins
/modules/common/.flutter-plugins-dependencies
Expand Down
7 changes: 3 additions & 4 deletions app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,8 @@ app.*.symbols
/env*.json

# Environment and configuration files (contain sensitive data)
env/.env
env/.settings
env/env_*.json
../env/.env
../env/env_*.json
.env
.env.*
*.env
Expand All @@ -141,4 +140,4 @@ firebase-debug.log

# Local development files
local.properties
*.properties
*.properties
13 changes: 13 additions & 0 deletions app/env/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# The .env file MUST be in .gitignore
# Delete this file after creating .env
#
# Each flavor (dev/qa/prod) uses the same .env file but reads
# different variables based on the suffix:
# - Development (main_dev.dart) → reads *_DEV variables
# - QA/Staging (main_qa.dart) → reads *_QA variables
# - Production (main.dart) → reads *_PROD variables

API_URL_DEV=https://your-api-url-dev.com
API_URL_QA=https://your-api-url-qa.com
API_URL_PROD=https://your-api-url-prod.com

38 changes: 36 additions & 2 deletions app/lib/main/env/env_config.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'package:domain/env/env_config.dart';

enum Flavor { dev, qa, prod }

class FlavorValues {
final String baseUrl;

FlavorValues({required this.baseUrl});
FlavorValues();
}

class FlavorConfig {
Expand All @@ -22,6 +23,17 @@ class FlavorConfig {
flavor.toString(),
values,
);
switch(flavor) {
case Flavor.dev:
EnvConfig.env = EnvConfig.kDevEnv;
break;
case Flavor.qa:
EnvConfig.env = EnvConfig.kQaEnv;
break;
case Flavor.prod:
EnvConfig.env = EnvConfig.kProdEnv;
break;
}
return _instance!;
}

Expand All @@ -34,4 +46,26 @@ class FlavorConfig {
static bool isDevelopment() => instance.flavor == Flavor.dev;

static bool isQA() => instance.flavor == Flavor.qa;

/// Returns the environment file path (single .env file for all flavors)
///
/// SETUP INSTRUCTIONS:
/// 1. Navigate to app/env/ directory
/// 2. Create a .env file (must be in .gitignore):
/// cp .env.example .env
/// 3. Add your environment variables with flavor suffixes:
/// API_URL_DEV=https://dev-api.example.com
/// API_URL_QA=https://qa-api.example.com
/// API_URL_PROD=https://api.example.com
/// 4. The active flavor determines which suffix is used (_DEV, _QA, _PROD)
/// 5. Access variables in domain/lib/env/env_config.dart using:
/// static String get apiUrl => dotenv.env['API_URL_$env']?.toString() ?? '';
/// (The $env automatically appends DEV, QA, or PROD based on active flavor)
/// 6. Go to pubspec.yaml and add the following:
/// assets:
/// - env/.env

static String getEnvFilePath() {
return 'env/.env.example';
}
}
3 changes: 1 addition & 2 deletions app/lib/main/env/main.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import 'package:flutter/material.dart';
import 'package:app/main/env/env_config.dart';
import 'package:app/main/init.dart';
import 'package:domain/env.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlavorConfig(
flavor: Flavor.prod,
values: FlavorValues(baseUrl: EnvConfig.apiUrl),
values: FlavorValues(),
);
//Add your firebase configuration here
/*await Firebase.initializeApp(
Expand Down
2 changes: 1 addition & 1 deletion app/lib/main/env/main_dev.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlavorConfig(
flavor: Flavor.dev,
values: FlavorValues(baseUrl: "https://demo_dev/web_api.json"),
values: FlavorValues(),
);
//Add your firebase configuration here
/*await Firebase.initializeApp(
Expand Down
2 changes: 1 addition & 1 deletion app/lib/main/env/main_qa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlavorConfig(
flavor: Flavor.qa,
values: FlavorValues(baseUrl: "https://demo_qa/web_api.json"),
values: FlavorValues(),
);
//Add your firebase configuration here
/*await Firebase.initializeApp(
Expand Down
6 changes: 5 additions & 1 deletion app/lib/main/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import 'package:domain/init.dart';
import 'package:example_domain/init.dart';
import 'package:example_data/init.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_it/get_it.dart';
import 'package:url_strategy/url_strategy.dart';

import 'env/env_config.dart';

void init() async {
WidgetsFlutterBinding.ensureInitialized();
await initialize();
Expand All @@ -18,11 +21,12 @@ void init() async {
final getIt = GetIt.instance;

Future<void> initialize() async {
await dotenv.load(fileName: FlavorConfig.getEnvFilePath());
await CommonInit.initialize(getIt);
await DataInit.initialize(getIt);
await DomainInit.initialize(getIt);

// Example Module init
await ExampleDomainInit.initialize(getIt);
await ExampleDataInit.initialize(getIt);
await ExampleDomainInit.initialize(getIt);
}
48 changes: 48 additions & 0 deletions app/lib/presentation/ui/custom/environment_selector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:domain/env/env_config.dart';
import 'package:domain/services/environment_service.dart';
import 'package:flutter/material.dart';

import '../../../main/init.dart';

class EnvironmentSelector extends StatelessWidget {
EnvironmentSelector({
super.key,
});

final EnvironmentService environmentService = getIt<EnvironmentService>();

DropdownMenuItem<String> _item(
String value, String label, TextStyle textStyle) =>
DropdownMenuItem<String>(
value: value,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(label, style: textStyle),
),
);

@override
Widget build(BuildContext context) {
final textStyle =
Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.black);

final items = <DropdownMenuItem<String>>[
_item(EnvConfig.kDevEnv, 'Development', textStyle!),
_item(EnvConfig.kQaEnv, 'QA', textStyle),
_item(EnvConfig.kProdEnv, 'Production', textStyle),
];

return DropdownButtonFormField<String>(
initialValue: EnvConfig.env,
style: textStyle,
decoration: InputDecoration(
labelText: 'Environment',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.settings),
labelStyle: textStyle,
),
items: items,
onChanged: (value) => environmentService.setEnvironment(value!),
);
}
}
11 changes: 9 additions & 2 deletions app/lib/presentation/ui/pages/login/login_page.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'package:app/main/init.dart';
import 'package:app/presentation/themes/app_themes.dart';
import 'package:app/presentation/ui/custom/app_theme_switch.dart';
import 'package:app/presentation/ui/custom/loading_screen.dart';
import 'package:common/core/resource.dart';
import 'package:domain/bloc/auth/auth_cubit.dart';
import 'package:domain/services/auth_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:app/presentation/ui/custom/app_theme_switch.dart';
import 'package:app/presentation/ui/custom/loading_screen.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../custom/environment_selector.dart';

class LoginPage extends StatelessWidget {
AuthService get _authService => getIt();

Expand Down Expand Up @@ -39,6 +42,10 @@ class LoginPage extends StatelessWidget {
},
),
),
const SizedBox(height: 32),
if (kDebugMode) ...[
EnvironmentSelector(),
],
],
),
),
Expand Down
3 changes: 3 additions & 0 deletions app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies:
equatable: ^2.0.5
firebase_core: ^3.13.0
url_strategy: ^0.2.0
flutter_dotenv: ^5.2.1
flutter_localizations:
sdk: flutter
domain:
Expand Down Expand Up @@ -82,6 +83,8 @@ flutter:
# Remove example assets.
assets:
- assets/images/
# remove example suffix
- env/.env.example
fonts:
- family: Roboto Black
fonts:
Expand Down
6 changes: 0 additions & 6 deletions modules/common/.flutter-plugins

This file was deleted.

14 changes: 0 additions & 14 deletions modules/data/.flutter-plugins

This file was deleted.

16 changes: 11 additions & 5 deletions modules/data/lib/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ import 'package:data/repositories/common_repository_impl.dart';
import 'package:dio/dio.dart';
import 'package:domain/repositories/auth_repository.dart';
import 'package:domain/repositories/common_repository.dart';
import 'package:domain/services/environment_service.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'network/config/environment_service_impl.dart';
import 'network/interceptors/auth_token_interceptor.dart';

class DataInit {
static Future<void> initialize(GetIt getIt) async {
final pref = await SharedPreferences.getInstance();

getIt.registerSingleton<SharedPreferences>(pref);
getIt.registerSingleton<Preferences>(PreferencesImpl(getIt()));

//Network
getIt.registerLazySingleton<Dio>(() => NetworkConfig.provideDio());
// Network
getIt.registerLazySingleton<AuthTokenInterceptor>(() => AuthTokenInterceptor(getIt()));
getIt.registerLazySingleton<Dio>(() => NetworkConfig.provideDio(getIt()));
getIt.registerLazySingleton<EnvironmentService>(() => EnvironmentServiceImpl(getIt()));

//Data Sources
// Data Sources

//Repositories
// Repositories
getIt.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(getIt()),
() => AuthRepositoryImpl(getIt(),),
);
getIt.registerLazySingleton<CommonRepository>(
() => CommonRepositoryImpl(getIt()),
Expand Down
16 changes: 16 additions & 0 deletions modules/data/lib/network/config/environment_service_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:data/network/config/network_config.dart';
import 'package:dio/dio.dart';
import 'package:domain/env/env_config.dart';
import 'package:domain/services/environment_service.dart';

class EnvironmentServiceImpl extends EnvironmentService {
final Dio _dio;

EnvironmentServiceImpl(this._dio);

@override
void setEnvironment(String env) {
EnvConfig.env = env;
NetworkConfig.updateBaseUrl(_dio);
}
}
32 changes: 29 additions & 3 deletions modules/data/lib/network/config/network_config.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import 'package:dio/dio.dart';
import 'package:domain/env/env_config.dart';
import 'package:flutter/foundation.dart';

import '../interceptors/auth_token_interceptor.dart';
import 'network_constants.dart';

class NetworkConfig {
static Dio provideDio() {
static Dio provideDio(AuthTokenInterceptor? authTokenInterceptor) {
final options = BaseOptions(
baseUrl: NetworkConstants.baseUrl,
baseUrl: EnvConfig.apiUrl,
connectTimeout: const Duration(
seconds: NetworkConstants.connectTimeout,
),
receiveTimeout: const Duration(
seconds: NetworkConstants.receiveTimeout,
),
);
return Dio(options);

final dio = Dio(options);

// Add debug logging only in debug mode
if (kDebugMode) {
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
requestHeader: true,
responseHeader: true,
error: true,
logPrint: (object) => debugPrint(object.toString()),
));
}

if (authTokenInterceptor != null) {
dio.interceptors.add(authTokenInterceptor);
}

return dio;
}

static void updateBaseUrl(Dio dio) {
dio.options.baseUrl = EnvConfig.apiUrl;
}
}
6 changes: 6 additions & 0 deletions modules/data/lib/network/config/network_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ class NetworkConstants {
//static const baseUrl = String.fromEnvironment('API_URL', defaultValue: "NA");
static const productsPath = "/products";
static const baseUrl = "http://www.example.com"; // To get started: comment this and uncomment line 4
static const tokenHeader = "token";
static const unauthorizedStatusCode = 401;
static const forbiddenStatusCode = 403;
static const invalidStatusCode = 422;
static const contentTypeHeader = "Content-Type";
static const applicationJsonContentType = "application/json";
}
Loading
Loading