fill
This commit is contained in:
538
lib/core/di/providers.dart
Normal file
538
lib/core/di/providers.dart
Normal file
@@ -0,0 +1,538 @@
|
||||
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/repositories/products_repository.dart';
|
||||
import '../../features/products/domain/usecases/get_products_usecase.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 '../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);
|
||||
});
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
/// ========================================================================
|
||||
/// 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
|
||||
///
|
||||
Reference in New Issue
Block a user