# Dependency Injection Architecture ## Provider Dependency Graph ``` ┌─────────────────────────────────────────────────────────────────────┐ │ PRESENTATION LAYER │ │ (UI State Management) │ └─────────────────────────────────────────────────────────────────────┘ │ │ depends on ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ DOMAIN LAYER │ │ (Business Logic) │ └─────────────────────────────────────────────────────────────────────┘ │ │ depends on ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ DATA LAYER │ │ (Repositories & Data Sources) │ └─────────────────────────────────────────────────────────────────────┘ │ │ depends on ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ CORE LAYER │ │ (Infrastructure Services) │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Complete Provider Dependency Tree ### Authentication Feature ``` secureStorageProvider (Core - Singleton) │ ├──> apiClientProvider (Core - Singleton) │ │ │ └──> authRemoteDataSourceProvider │ │ └─────────────────────────────┴──> authRepositoryProvider │ ┌──────────────────────────────┼────────────────────────────┐ │ │ │ loginUseCaseProvider logoutUseCaseProvider checkAuthStatusUseCaseProvider │ │ │ └──────────────────────────────┴────────────────────────────┘ │ authProvider (StateNotifier) │ ┌──────────────────────────────┼────────────────────────────┐ │ │ │ isAuthenticatedProvider currentUserProvider authErrorProvider ``` ### Warehouse Feature ``` apiClientProvider (Core - Singleton) │ └──> warehouseRemoteDataSourceProvider │ └──> warehouseRepositoryProvider │ └──> getWarehousesUseCaseProvider │ └──> warehouseProvider (StateNotifier) │ ┌──────────────────────────────────────────┼────────────────────────────────┐ │ │ │ warehousesListProvider selectedWarehouseProvider isWarehouseLoadingProvider ``` ### Products Feature ``` apiClientProvider (Core - Singleton) │ └──> productsRemoteDataSourceProvider │ └──> productsRepositoryProvider │ └──> getProductsUseCaseProvider │ └──> productsProvider (StateNotifier) │ ┌──────────────────────────────────────────┼────────────────────────────────┐ │ │ │ productsListProvider operationTypeProvider isProductsLoadingProvider ``` ## Layer-by-Layer Architecture ### 1. Core Layer (Infrastructure) **Purpose**: Provide foundational services that all features depend on **Providers**: - `secureStorageProvider` - Manages encrypted storage - `apiClientProvider` - HTTP client with auth interceptors **Characteristics**: - Singleton instances - No business logic - Pure infrastructure - Used by all features **Example**: ```dart final secureStorageProvider = Provider((ref) { return SecureStorage(); }); final apiClientProvider = Provider((ref) { final secureStorage = ref.watch(secureStorageProvider); return ApiClient(secureStorage); }); ``` ### 2. Data Layer **Purpose**: Handle data operations - API calls, local storage, caching **Components**: - **Remote Data Sources**: Make API calls - **Repositories**: Coordinate data sources, convert models to entities **Providers**: - `xxxRemoteDataSourceProvider` - API client wrappers - `xxxRepositoryProvider` - Repository implementations **Characteristics**: - Depends on Core layer - Implements Domain interfaces - Handles data transformation - Manages errors (exceptions → failures) **Example**: ```dart // Data Source final authRemoteDataSourceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); return AuthRemoteDataSourceImpl(apiClient); }); // Repository final authRepositoryProvider = Provider((ref) { final remoteDataSource = ref.watch(authRemoteDataSourceProvider); final secureStorage = ref.watch(secureStorageProvider); return AuthRepositoryImpl( remoteDataSource: remoteDataSource, secureStorage: secureStorage, ); }); ``` ### 3. Domain Layer (Business Logic) **Purpose**: Encapsulate business rules and use cases **Components**: - **Entities**: Pure business objects - **Repository Interfaces**: Define data contracts - **Use Cases**: Single-purpose business operations **Providers**: - `xxxUseCaseProvider` - Business logic encapsulation **Characteristics**: - No external dependencies (pure Dart) - Depends only on abstractions - Contains business rules - Reusable across features - Testable in isolation **Example**: ```dart final loginUseCaseProvider = Provider((ref) { final repository = ref.watch(authRepositoryProvider); return LoginUseCase(repository); }); final getWarehousesUseCaseProvider = Provider((ref) { final repository = ref.watch(warehouseRepositoryProvider); return GetWarehousesUseCase(repository); }); ``` ### 4. Presentation Layer (UI State) **Purpose**: Manage UI state and handle user interactions **Components**: - **State Classes**: Immutable state containers - **State Notifiers**: Mutable state managers - **Derived Providers**: Computed state values **Providers**: - `xxxProvider` (StateNotifier) - Main state management - `isXxxLoadingProvider` - Loading state - `xxxErrorProvider` - Error state - `xxxListProvider` - Data lists **Characteristics**: - Depends on Domain layer - Manages UI state - Handles user actions - Notifies UI of changes - Can depend on multiple use cases **Example**: ```dart // Main state notifier final authProvider = StateNotifierProvider((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, ); }); // Derived providers final isAuthenticatedProvider = Provider((ref) { final authState = ref.watch(authProvider); return authState.isAuthenticated; }); ``` ## Data Flow Patterns ### 1. User Action Flow (Write Operation) ``` ┌──────────────┐ │ UI Widget │ └──────┬───────┘ │ User taps button ▼ ┌──────────────────────┐ │ ref.read(provider │ │ .notifier) │ │ .someMethod() │ └──────┬───────────────┘ │ ▼ ┌──────────────────────┐ │ StateNotifier │ │ - Set loading state │ └──────┬───────────────┘ │ ▼ ┌──────────────────────┐ │ Use Case │ │ - Validate input │ │ - Business logic │ └──────┬───────────────┘ │ ▼ ┌──────────────────────┐ │ Repository │ │ - Coordinate data │ │ - Error handling │ └──────┬───────────────┘ │ ▼ ┌──────────────────────┐ │ Data Source │ │ - API call │ │ - Parse response │ └──────┬───────────────┘ │ ▼ ┌──────────────────────┐ │ API Client │ │ - HTTP request │ │ - Add auth token │ └──────┬───────────────┘ │ │ Response ←───── ▼ ┌──────────────────────┐ │ StateNotifier │ │ - Update state │ │ - Notify listeners │ └──────┬───────────────┘ │ ▼ ┌──────────────────────┐ │ UI Widget │ │ - Rebuild with │ │ new state │ └──────────────────────┘ ``` ### 2. State Observation Flow (Read Operation) ``` ┌──────────────────────┐ │ UI Widget │ │ ref.watch(provider) │ └──────┬───────────────┘ │ Subscribes to ▼ ┌──────────────────────┐ │ StateNotifier │ │ Current State │ └──────┬───────────────┘ │ State changes ▼ ┌──────────────────────┐ │ UI Widget │ │ Automatically │ │ rebuilds │ └──────────────────────┘ ``` ## Provider Types Usage ### Provider (Immutable Services) **Use for**: Services, repositories, use cases, utilities ```dart final myServiceProvider = Provider((ref) { final dependency = ref.watch(dependencyProvider); return MyService(dependency); }); ``` **Lifecycle**: Created once, lives forever (unless autoDispose) ### StateNotifierProvider (Mutable State) **Use for**: Managing feature state that changes over time ```dart final myStateProvider = StateNotifierProvider((ref) { final useCase = ref.watch(useCaseProvider); return MyNotifier(useCase); }); ``` **Lifecycle**: Created on first access, disposed when no longer used ### Derived Providers (Computed Values) **Use for**: Computed values from other providers ```dart final derivedProvider = Provider((ref) { final state = ref.watch(stateProvider); return computeValue(state); }); ``` **Lifecycle**: Recomputed when dependencies change ## Best Practices by Layer ### Core Layer ✅ Keep providers pure and stateless ✅ Use singleton pattern ✅ No business logic ❌ Don't depend on feature providers ❌ Don't manage mutable state ### Data Layer ✅ Implement domain interfaces ✅ Convert models ↔ entities ✅ Handle all exceptions ✅ Use Either return type ❌ Don't expose models to domain ❌ Don't contain business logic ### Domain Layer ✅ Pure Dart (no Flutter dependencies) ✅ Single responsibility per use case ✅ Validate input ✅ Return Either ❌ Don't know about UI ❌ Don't know about data sources ### Presentation Layer ✅ Manage UI-specific state ✅ Call multiple use cases if needed ✅ Transform data for display ✅ Handle navigation logic ❌ Don't access data sources directly ❌ Don't perform business logic ## Testing Strategy ### Unit Testing **Core Layer**: Test utilities and services ```dart test('SecureStorage saves token', () async { final storage = SecureStorage(); await storage.saveAccessToken('token'); expect(await storage.getAccessToken(), 'token'); }); ``` **Domain Layer**: Test use cases with mock repositories ```dart test('LoginUseCase returns user on success', () async { final mockRepo = MockAuthRepository(); when(mockRepo.login(any)).thenAnswer((_) async => Right(mockUser)); final useCase = LoginUseCase(mockRepo); final result = await useCase(loginRequest); expect(result.isRight(), true); }); ``` **Presentation Layer**: Test state notifiers ```dart test('AuthNotifier sets authenticated on login', () async { final container = ProviderContainer( overrides: [ loginUseCaseProvider.overrideWithValue(mockLoginUseCase), ], ); await container.read(authProvider.notifier).login('user', 'pass'); expect(container.read(isAuthenticatedProvider), true); }); ``` ### Widget Testing ```dart testWidgets('Login page shows error on failure', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ authRepositoryProvider.overrideWithValue(mockAuthRepository), ], child: MaterialApp(home: LoginPage()), ), ); // Interact with widget await tester.tap(find.text('Login')); await tester.pump(); // Verify error is shown expect(find.text('Invalid credentials'), findsOneWidget); }); ``` ## Performance Optimization ### 1. Use Derived Providers Instead of computing in build(): ```dart // ❌ Bad - computes every rebuild @override Widget build(BuildContext context, WidgetRef ref) { final warehouses = ref.watch(warehousesListProvider); final ngWarehouses = warehouses.where((w) => w.isNGWareHouse).toList(); return ListView(...); } // ✅ Good - computed once per state change final ngWarehousesProvider = Provider>((ref) { final warehouses = ref.watch(warehousesListProvider); return warehouses.where((w) => w.isNGWareHouse).toList(); }); @override Widget build(BuildContext context, WidgetRef ref) { final ngWarehouses = ref.watch(ngWarehousesProvider); return ListView(...); } ``` ### 2. Use select() for Partial State ```dart // ❌ Bad - rebuilds on any state change final authState = ref.watch(authProvider); final isLoading = authState.isLoading; // ✅ Good - rebuilds only when isLoading changes final isLoading = ref.watch(authProvider.select((s) => s.isLoading)); ``` ### 3. Use autoDispose for Temporary Providers ```dart final temporaryProvider = Provider.autoDispose((ref) { final service = MyService(); ref.onDispose(() { service.dispose(); }); return service; }); ``` ## Common Patterns ### Pattern: Feature Initialization ```dart @override void initState() { super.initState(); Future.microtask(() { ref.read(warehouseProvider.notifier).loadWarehouses(); }); } ``` ### Pattern: Conditional Navigation ```dart ref.listen(authProvider, (previous, next) { if (next.isAuthenticated && !(previous?.isAuthenticated ?? false)) { Navigator.pushReplacementNamed(context, '/home'); } }); ``` ### Pattern: Error Handling ```dart ref.listen(authErrorProvider, (previous, next) { if (next != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(next)), ); } }); ``` ### Pattern: Pull-to-Refresh ```dart RefreshIndicator( onRefresh: () async { await ref.read(warehouseProvider.notifier).refresh(); }, child: ListView(...), ) ``` ## Dependency Injection Benefits 1. **Testability**: Easy to mock dependencies 2. **Maintainability**: Clear dependency tree 3. **Scalability**: Add features without touching existing code 4. **Flexibility**: Swap implementations easily 5. **Readability**: Explicit dependencies 6. **Type Safety**: Compile-time checks 7. **Hot Reload**: Works seamlessly with Flutter 8. **DevTools**: Inspect state in real-time ## Summary This DI architecture provides: - Clear separation of concerns - Predictable data flow - Easy testing at all levels - Type-safe dependency injection - Reactive state management - Scalable feature structure For more details, see: - [README.md](./README.md) - Comprehensive guide - [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) - Quick lookup