Files
minhthu/lib/core/di/providers.dart
Phuoc Nguyen 5cfc56f40d add dropdown
2025-10-28 16:24:17 +07:00

698 lines
24 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../features/auth/data/datasources/auth_remote_datasource.dart';
import '../../features/auth/data/repositories/auth_repository_impl.dart';
import '../../features/auth/domain/repositories/auth_repository.dart';
import '../../features/auth/domain/usecases/login_usecase.dart';
import '../../features/auth/presentation/providers/auth_provider.dart';
import '../../features/products/data/datasources/products_remote_datasource.dart';
import '../../features/products/data/repositories/products_repository_impl.dart';
import '../../features/products/domain/entities/product_stage_entity.dart';
import '../../features/products/domain/repositories/products_repository.dart';
import '../../features/products/domain/usecases/get_product_detail_usecase.dart';
import '../../features/products/domain/usecases/get_products_usecase.dart';
import '../../features/products/presentation/providers/product_detail_provider.dart';
import '../../features/products/presentation/providers/products_provider.dart';
import '../../features/warehouse/data/datasources/warehouse_remote_datasource.dart';
import '../../features/warehouse/data/repositories/warehouse_repository_impl.dart';
import '../../features/warehouse/domain/repositories/warehouse_repository.dart';
import '../../features/warehouse/domain/usecases/get_warehouses_usecase.dart';
import '../../features/warehouse/presentation/providers/warehouse_provider.dart';
import '../../features/users/data/datasources/users_local_datasource.dart';
import '../../features/users/data/datasources/users_remote_datasource.dart';
import '../../features/users/data/repositories/users_repository_impl.dart';
import '../../features/users/domain/repositories/users_repository.dart';
import '../../features/users/domain/usecases/get_users_usecase.dart';
import '../../features/users/domain/usecases/sync_users_usecase.dart';
import '../../features/users/presentation/providers/users_provider.dart';
import '../../features/users/presentation/providers/users_state.dart';
import '../network/api_client.dart';
import '../storage/secure_storage.dart';
/// ========================================================================
/// CORE PROVIDERS
/// ========================================================================
/// These are singleton providers for core infrastructure services
/// Secure storage provider (Singleton)
/// Provides secure storage for sensitive data like tokens
final secureStorageProvider = Provider<SecureStorage>((ref) {
return SecureStorage();
});
/// API client provider (Singleton)
/// Provides HTTP client with authentication and error handling
/// Depends on SecureStorage for token management
final apiClientProvider = Provider<ApiClient>((ref) {
final secureStorage = ref.watch(secureStorageProvider);
return ApiClient(secureStorage);
});
/// ========================================================================
/// AUTH FEATURE PROVIDERS
/// ========================================================================
/// Providers for authentication feature following clean architecture
// Data Layer
/// Auth remote data source provider
/// Handles API calls for authentication
final authRemoteDataSourceProvider = Provider<AuthRemoteDataSource>((ref) {
final apiClient = ref.watch(apiClientProvider);
return AuthRemoteDataSourceImpl(apiClient);
});
/// Auth repository provider
/// Implements domain repository interface
/// Coordinates between data sources and handles error conversion
final authRepositoryProvider = Provider<AuthRepository>((ref) {
final remoteDataSource = ref.watch(authRemoteDataSourceProvider);
final secureStorage = ref.watch(secureStorageProvider);
return AuthRepositoryImpl(
remoteDataSource: remoteDataSource,
secureStorage: secureStorage,
);
});
// Domain Layer
/// Login use case provider
/// Encapsulates login business logic
final loginUseCaseProvider = Provider<LoginUseCase>((ref) {
final repository = ref.watch(authRepositoryProvider);
return LoginUseCase(repository);
});
/// Logout use case provider
/// Encapsulates logout business logic
final logoutUseCaseProvider = Provider<LogoutUseCase>((ref) {
final repository = ref.watch(authRepositoryProvider);
return LogoutUseCase(repository);
});
/// Check auth status use case provider
/// Checks if user is authenticated
final checkAuthStatusUseCaseProvider = Provider<CheckAuthStatusUseCase>((ref) {
final repository = ref.watch(authRepositoryProvider);
return CheckAuthStatusUseCase(repository);
});
/// Get current user use case provider
/// Retrieves current user data from storage
final getCurrentUserUseCaseProvider = Provider<GetCurrentUserUseCase>((ref) {
final repository = ref.watch(authRepositoryProvider);
return GetCurrentUserUseCase(repository);
});
/// Refresh token use case provider
/// Refreshes access token using refresh token
final refreshTokenUseCaseProvider = Provider<RefreshTokenUseCase>((ref) {
final repository = ref.watch(authRepositoryProvider);
return RefreshTokenUseCase(repository);
});
// Presentation Layer
/// Auth state notifier provider
/// Manages authentication state across the app
/// This is the main provider to use in UI for auth state
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final loginUseCase = ref.watch(loginUseCaseProvider);
final logoutUseCase = ref.watch(logoutUseCaseProvider);
final checkAuthStatusUseCase = ref.watch(checkAuthStatusUseCaseProvider);
final getCurrentUserUseCase = ref.watch(getCurrentUserUseCaseProvider);
return AuthNotifier(
loginUseCase: loginUseCase,
logoutUseCase: logoutUseCase,
checkAuthStatusUseCase: checkAuthStatusUseCase,
getCurrentUserUseCase: getCurrentUserUseCase,
);
});
/// Convenient providers for auth state
/// Provider to check if user is authenticated
/// Usage: ref.watch(isAuthenticatedProvider)
final isAuthenticatedProvider = Provider<bool>((ref) {
final authState = ref.watch(authProvider);
return authState.isAuthenticated;
});
/// Provider to get current user
/// Returns null if user is not authenticated
/// Usage: ref.watch(currentUserProvider)
final currentUserProvider = Provider((ref) {
final authState = ref.watch(authProvider);
return authState.user;
});
/// Provider to check if auth is loading
/// Usage: ref.watch(isAuthLoadingProvider)
final isAuthLoadingProvider = Provider<bool>((ref) {
final authState = ref.watch(authProvider);
return authState.isLoading;
});
/// Provider to get auth error
/// Returns null if no error
/// Usage: ref.watch(authErrorProvider)
final authErrorProvider = Provider<String?>((ref) {
final authState = ref.watch(authProvider);
return authState.error;
});
/// ========================================================================
/// WAREHOUSE FEATURE PROVIDERS
/// ========================================================================
/// Providers for warehouse feature following clean architecture
// Data Layer
/// Warehouse remote data source provider
/// Handles API calls for warehouses
final warehouseRemoteDataSourceProvider =
Provider<WarehouseRemoteDataSource>((ref) {
final apiClient = ref.watch(apiClientProvider);
return WarehouseRemoteDataSourceImpl(apiClient);
});
/// Warehouse repository provider
/// Implements domain repository interface
final warehouseRepositoryProvider = Provider<WarehouseRepository>((ref) {
final remoteDataSource = ref.watch(warehouseRemoteDataSourceProvider);
return WarehouseRepositoryImpl(remoteDataSource);
});
// Domain Layer
/// Get warehouses use case provider
/// Encapsulates warehouse fetching business logic
final getWarehousesUseCaseProvider = Provider<GetWarehousesUseCase>((ref) {
final repository = ref.watch(warehouseRepositoryProvider);
return GetWarehousesUseCase(repository);
});
// Presentation Layer
/// Warehouse state notifier provider
/// Manages warehouse state including list and selection
final warehouseProvider =
StateNotifierProvider<WarehouseNotifier, WarehouseState>((ref) {
final getWarehousesUseCase = ref.watch(getWarehousesUseCaseProvider);
return WarehouseNotifier(getWarehousesUseCase);
});
/// Convenient providers for warehouse state
/// Provider to get list of warehouses
/// Usage: ref.watch(warehousesListProvider)
final warehousesListProvider = Provider((ref) {
final warehouseState = ref.watch(warehouseProvider);
return warehouseState.warehouses;
});
/// Provider to get selected warehouse
/// Returns null if no warehouse is selected
/// Usage: ref.watch(selectedWarehouseProvider)
final selectedWarehouseProvider = Provider((ref) {
final warehouseState = ref.watch(warehouseProvider);
return warehouseState.selectedWarehouse;
});
/// Provider to check if warehouses are loading
/// Usage: ref.watch(isWarehouseLoadingProvider)
final isWarehouseLoadingProvider = Provider<bool>((ref) {
final warehouseState = ref.watch(warehouseProvider);
return warehouseState.isLoading;
});
/// Provider to check if warehouses have been loaded
/// Usage: ref.watch(hasWarehousesProvider)
final hasWarehousesProvider = Provider<bool>((ref) {
final warehouseState = ref.watch(warehouseProvider);
return warehouseState.hasWarehouses;
});
/// Provider to check if a warehouse is selected
/// Usage: ref.watch(hasWarehouseSelectionProvider)
final hasWarehouseSelectionProvider = Provider<bool>((ref) {
final warehouseState = ref.watch(warehouseProvider);
return warehouseState.hasSelection;
});
/// Provider to get warehouse error
/// Returns null if no error
/// Usage: ref.watch(warehouseErrorProvider)
final warehouseErrorProvider = Provider<String?>((ref) {
final warehouseState = ref.watch(warehouseProvider);
return warehouseState.error;
});
/// ========================================================================
/// PRODUCTS FEATURE PROVIDERS
/// ========================================================================
/// Providers for products feature following clean architecture
// Data Layer
/// Products remote data source provider
/// Handles API calls for products
final productsRemoteDataSourceProvider =
Provider<ProductsRemoteDataSource>((ref) {
final apiClient = ref.watch(apiClientProvider);
return ProductsRemoteDataSourceImpl(apiClient);
});
/// Products repository provider
/// Implements domain repository interface
final productsRepositoryProvider = Provider<ProductsRepository>((ref) {
final remoteDataSource = ref.watch(productsRemoteDataSourceProvider);
return ProductsRepositoryImpl(remoteDataSource);
});
// Domain Layer
/// Get products use case provider
/// Encapsulates product fetching business logic
final getProductsUseCaseProvider = Provider<GetProductsUseCase>((ref) {
final repository = ref.watch(productsRepositoryProvider);
return GetProductsUseCase(repository);
});
/// Get product detail use case provider
/// Encapsulates product detail fetching business logic
final getProductDetailUseCaseProvider = Provider<GetProductDetailUseCase>((ref) {
final repository = ref.watch(productsRepositoryProvider);
return GetProductDetailUseCase(repository);
});
// Presentation Layer
/// Products state notifier provider
/// Manages products state including list, loading, and errors
final productsProvider =
StateNotifierProvider<ProductsNotifier, ProductsState>((ref) {
final getProductsUseCase = ref.watch(getProductsUseCaseProvider);
return ProductsNotifier(getProductsUseCase);
});
/// Convenient providers for products state
/// Provider to get list of products
/// Usage: ref.watch(productsListProvider)
final productsListProvider = Provider((ref) {
final productsState = ref.watch(productsProvider);
return productsState.products;
});
/// Provider to get operation type (import/export)
/// Usage: ref.watch(operationTypeProvider)
final operationTypeProvider = Provider<String>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.operationType;
});
/// Provider to get warehouse ID for products
/// Returns null if no warehouse is set
/// Usage: ref.watch(productsWarehouseIdProvider)
final productsWarehouseIdProvider = Provider<int?>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.warehouseId;
});
/// Provider to get warehouse name for products
/// Returns null if no warehouse is set
/// Usage: ref.watch(productsWarehouseNameProvider)
final productsWarehouseNameProvider = Provider<String?>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.warehouseName;
});
/// Provider to check if products are loading
/// Usage: ref.watch(isProductsLoadingProvider)
final isProductsLoadingProvider = Provider<bool>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.isLoading;
});
/// Provider to check if products list has items
/// Usage: ref.watch(hasProductsProvider)
final hasProductsProvider = Provider<bool>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.products.isNotEmpty;
});
/// Provider to get products count
/// Usage: ref.watch(productsCountProvider)
final productsCountProvider = Provider<int>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.products.length;
});
/// Provider to get products error
/// Returns null if no error
/// Usage: ref.watch(productsErrorProvider)
final productsErrorProvider = Provider<String?>((ref) {
final productsState = ref.watch(productsProvider);
return productsState.error;
});
/// Product detail state notifier provider
/// Manages product detail state for a specific product in a warehouse
/// This needs to be a family provider to support multiple product details
final productDetailProvider =
StateNotifierProvider.family<ProductDetailNotifier, ProductDetailState, String>(
(ref, _) {
final getProductDetailUseCase = ref.watch(getProductDetailUseCaseProvider);
return ProductDetailNotifier(getProductDetailUseCase);
},
);
/// Convenient providers for product detail state
/// Provider to get product stages list
/// Usage: ref.watch(productStagesProvider(key))
final productStagesProvider = Provider.family<List<ProductStageEntity>, String>((ref, key) {
final state = ref.watch(productDetailProvider(key));
return state.stages;
});
/// Provider to get selected product stage
/// Usage: ref.watch(selectedProductStageProvider(key))
final selectedProductStageProvider = Provider.family<ProductStageEntity?, String>((ref, key) {
final state = ref.watch(productDetailProvider(key));
return state.selectedStage;
});
/// Provider to check if product detail is loading
/// Usage: ref.watch(isProductDetailLoadingProvider(key))
final isProductDetailLoadingProvider = Provider.family<bool, String>((ref, key) {
final state = ref.watch(productDetailProvider(key));
return state.isLoading;
});
/// Provider to get product detail error
/// Returns null if no error
/// Usage: ref.watch(productDetailErrorProvider(key))
final productDetailErrorProvider = Provider.family<String?, String>((ref, key) {
final state = ref.watch(productDetailProvider(key));
return state.error;
});
/// ========================================================================
/// USERS FEATURE PROVIDERS
/// ========================================================================
/// Providers for users feature following clean architecture
// Data Layer
/// Users local data source provider
/// Handles local storage operations for users using Hive
final usersLocalDataSourceProvider = Provider<UsersLocalDataSource>((ref) {
return UsersLocalDataSourceImpl();
});
/// Users remote data source provider
/// Handles API calls for users
final usersRemoteDataSourceProvider = Provider<UsersRemoteDataSource>((ref) {
final apiClient = ref.watch(apiClientProvider);
return UsersRemoteDataSourceImpl(apiClient);
});
/// Users repository provider
/// Implements domain repository interface
/// Coordinates between local and remote data sources
final usersRepositoryProvider = Provider<UsersRepository>((ref) {
final remoteDataSource = ref.watch(usersRemoteDataSourceProvider);
final localDataSource = ref.watch(usersLocalDataSourceProvider);
return UsersRepositoryImpl(
remoteDataSource: remoteDataSource,
localDataSource: localDataSource,
);
});
// Domain Layer
/// Get users use case provider
/// Encapsulates user fetching business logic (local-first strategy)
final getUsersUseCaseProvider = Provider<GetUsersUseCase>((ref) {
final repository = ref.watch(usersRepositoryProvider);
return GetUsersUseCase(repository);
});
/// Sync users use case provider
/// Encapsulates user syncing business logic (force refresh from API)
final syncUsersUseCaseProvider = Provider<SyncUsersUseCase>((ref) {
final repository = ref.watch(usersRepositoryProvider);
return SyncUsersUseCase(repository);
});
// Presentation Layer
/// Users state notifier provider
/// Manages users state including list, loading, and errors
final usersProvider = StateNotifierProvider<UsersNotifier, UsersState>((ref) {
final getUsersUseCase = ref.watch(getUsersUseCaseProvider);
final syncUsersUseCase = ref.watch(syncUsersUseCaseProvider);
return UsersNotifier(
getUsersUseCase: getUsersUseCase,
syncUsersUseCase: syncUsersUseCase,
);
});
/// Convenient providers for users state
/// Provider to get list of users
/// Usage: ref.watch(usersListProvider)
final usersListProvider = Provider((ref) {
final usersState = ref.watch(usersProvider);
return usersState.users;
});
/// Provider to check if users are loading
/// Usage: ref.watch(isUsersLoadingProvider)
final isUsersLoadingProvider = Provider<bool>((ref) {
final usersState = ref.watch(usersProvider);
return usersState.isLoading;
});
/// Provider to check if users list has items
/// Usage: ref.watch(hasUsersProvider)
final hasUsersProvider = Provider<bool>((ref) {
final usersState = ref.watch(usersProvider);
return usersState.users.isNotEmpty;
});
/// Provider to get users count
/// Usage: ref.watch(usersCountProvider)
final usersCountProvider = Provider<int>((ref) {
final usersState = ref.watch(usersProvider);
return usersState.users.length;
});
/// Provider to get users error
/// Returns null if no error
/// Usage: ref.watch(usersErrorProvider)
final usersErrorProvider = Provider<String?>((ref) {
final usersState = ref.watch(usersProvider);
return usersState.error;
});
/// ========================================================================
/// USAGE EXAMPLES
/// ========================================================================
///
/// 1. Authentication Example:
/// ```dart
/// // In your LoginPage
/// class LoginPage extends ConsumerWidget {
/// @override
/// Widget build(BuildContext context, WidgetRef ref) {
/// final isAuthenticated = ref.watch(isAuthenticatedProvider);
/// final isLoading = ref.watch(isAuthLoadingProvider);
/// final error = ref.watch(authErrorProvider);
///
/// return Scaffold(
/// body: Column(
/// children: [
/// if (error != null) Text(error, style: errorStyle),
/// ElevatedButton(
/// onPressed: isLoading
/// ? null
/// : () => ref.read(authProvider.notifier).login(
/// username,
/// password,
/// ),
/// child: isLoading ? CircularProgressIndicator() : Text('Login'),
/// ),
/// ],
/// ),
/// );
/// }
/// }
/// ```
///
/// 2. Warehouse Selection Example:
/// ```dart
/// // In your WarehouseSelectionPage
/// class WarehouseSelectionPage extends ConsumerWidget {
/// @override
/// Widget build(BuildContext context, WidgetRef ref) {
/// final warehouses = ref.watch(warehousesListProvider);
/// final isLoading = ref.watch(isWarehouseLoadingProvider);
/// final selectedWarehouse = ref.watch(selectedWarehouseProvider);
///
/// // Load warehouses on first build
/// ref.listen(warehouseProvider, (previous, next) {
/// if (previous?.warehouses.isEmpty ?? true && !next.isLoading) {
/// ref.read(warehouseProvider.notifier).loadWarehouses();
/// }
/// });
///
/// return Scaffold(
/// body: isLoading
/// ? CircularProgressIndicator()
/// : ListView.builder(
/// itemCount: warehouses.length,
/// itemBuilder: (context, index) {
/// final warehouse = warehouses[index];
/// return ListTile(
/// title: Text(warehouse.name),
/// selected: selectedWarehouse?.id == warehouse.id,
/// onTap: () {
/// ref.read(warehouseProvider.notifier)
/// .selectWarehouse(warehouse);
/// },
/// );
/// },
/// ),
/// );
/// }
/// }
/// ```
///
/// 3. Products List Example:
/// ```dart
/// // In your ProductsPage
/// class ProductsPage extends ConsumerWidget {
/// final int warehouseId;
/// final String warehouseName;
/// final String operationType;
///
/// const ProductsPage({
/// required this.warehouseId,
/// required this.warehouseName,
/// required this.operationType,
/// });
///
/// @override
/// Widget build(BuildContext context, WidgetRef ref) {
/// final products = ref.watch(productsListProvider);
/// final isLoading = ref.watch(isProductsLoadingProvider);
/// final error = ref.watch(productsErrorProvider);
///
/// // Load products on first build
/// useEffect(() {
/// ref.read(productsProvider.notifier).loadProducts(
/// warehouseId,
/// warehouseName,
/// operationType,
/// );
/// return null;
/// }, []);
///
/// return Scaffold(
/// appBar: AppBar(
/// title: Text('$warehouseName - $operationType'),
/// ),
/// body: isLoading
/// ? CircularProgressIndicator()
/// : error != null
/// ? Text(error)
/// : ListView.builder(
/// itemCount: products.length,
/// itemBuilder: (context, index) {
/// final product = products[index];
/// return ListTile(
/// title: Text(product.name),
/// subtitle: Text(product.code),
/// );
/// },
/// ),
/// );
/// }
/// }
/// ```
///
/// 4. Checking Auth Status on App Start:
/// ```dart
/// // In your main.dart or root widget
/// class App extends ConsumerWidget {
/// @override
/// Widget build(BuildContext context, WidgetRef ref) {
/// // Check auth status when app starts
/// useEffect(() {
/// ref.read(authProvider.notifier).checkAuthStatus();
/// return null;
/// }, []);
///
/// final isAuthenticated = ref.watch(isAuthenticatedProvider);
///
/// return MaterialApp(
/// home: isAuthenticated ? WarehouseSelectionPage() : LoginPage(),
/// );
/// }
/// }
/// ```
///
/// 5. Logout Example:
/// ```dart
/// // In any widget
/// ElevatedButton(
/// onPressed: () {
/// ref.read(authProvider.notifier).logout();
/// },
/// child: Text('Logout'),
/// )
/// ```
///
/// ========================================================================
/// ARCHITECTURE NOTES
/// ========================================================================
///
/// This DI setup follows Clean Architecture principles:
///
/// 1. **Separation of Concerns**:
/// - Data Layer: Handles API calls and data storage
/// - Domain Layer: Contains business logic and use cases
/// - Presentation Layer: Manages UI state
///
/// 2. **Dependency Direction**:
/// - Presentation depends on Domain
/// - Data depends on Domain
/// - Domain depends on nothing (pure business logic)
///
/// 3. **Provider Hierarchy**:
/// - Core providers (Storage, API) are singletons
/// - Data sources depend on API client
/// - Repositories depend on data sources
/// - Use cases depend on repositories
/// - State notifiers depend on use cases
///
/// 4. **State Management**:
/// - StateNotifierProvider for mutable state
/// - Provider for immutable dependencies
/// - Convenient providers for derived state
///
/// 5. **Testability**:
/// - All dependencies are injected
/// - Easy to mock for testing
/// - Each layer can be tested independently
///
/// 6. **Scalability**:
/// - Add new features by following the same pattern
/// - Clear structure for team collaboration
/// - Easy to understand and maintain
///