18 KiB
18 KiB
Warehouse Feature - Architecture Diagram
Clean Architecture Layers
┌─────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ (UI, State Management, User Interactions) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌──────────────────────────────────┐ │
│ │ WarehouseCard │ │ WarehouseSelectionPage │ │
│ │ - Shows warehouse │ │ - Displays warehouse list │ │
│ │ information │ │ - Handles user selection │ │
│ └─────────────────────┘ │ - Pull to refresh │ │
│ │ - Loading/Error/Empty states │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────┐ │
│ │ WarehouseNotifier │ │
│ │ (StateNotifier) │ │
│ │ - loadWarehouses() │ │
│ │ - selectWarehouse() │ │
│ │ - refresh() │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────┐ │
│ │ WarehouseState │ │
│ │ - warehouses: List │ │
│ │ - selectedWarehouse: Warehouse? │ │
│ │ - isLoading: bool │ │
│ │ - error: String? │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓ uses
┌─────────────────────────────────────────────────────────────────┐
│ DOMAIN LAYER │
│ (Business Logic, Entities, Use Cases) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ GetWarehousesUseCase │ │
│ │ - Encapsulates business logic for fetching warehouses │ │
│ │ - Single responsibility │ │
│ │ - Returns Either<Failure, List<WarehouseEntity>> │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ uses │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WarehouseRepository (Interface) │ │
│ │ + getWarehouses(): Either<Failure, List<Warehouse>> │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WarehouseEntity │ │
│ │ - id: int │ │
│ │ - name: String │ │
│ │ - code: String │ │
│ │ - description: String? │ │
│ │ - isNGWareHouse: bool │ │
│ │ - totalCount: int │ │
│ │ + hasItems: bool │ │
│ │ + isNGType: bool │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓ implements
┌─────────────────────────────────────────────────────────────────┐
│ DATA LAYER │
│ (API Calls, Data Sources, Models) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WarehouseRepositoryImpl │ │
│ │ - Implements WarehouseRepository interface │ │
│ │ - Coordinates data sources │ │
│ │ - Converts exceptions to failures │ │
│ │ - Maps models to entities │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ uses │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WarehouseRemoteDataSource (Interface) │ │
│ │ + getWarehouses(): Future<List<WarehouseModel>> │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WarehouseRemoteDataSourceImpl │ │
│ │ - Makes API calls using ApiClient │ │
│ │ - Parses ApiResponse wrapper │ │
│ │ - Throws ServerException or NetworkException │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ uses │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ WarehouseModel │ │
│ │ - Extends WarehouseEntity │ │
│ │ - Adds JSON serialization (fromJson, toJson) │ │
│ │ - Maps API fields to entity fields │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ uses │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ApiClient (Core) │ │
│ │ - Dio HTTP client wrapper │ │
│ │ - Adds authentication headers │ │
│ │ - Handles 401 errors │ │
│ │ - Logging and error handling │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Data Flow
1. Loading Warehouses Flow
User Action (Pull to Refresh / Page Load)
↓
WarehouseSelectionPage
↓ calls
ref.read(warehouseProvider.notifier).loadWarehouses()
↓
WarehouseNotifier.loadWarehouses()
↓ sets state
state = state.setLoading() → UI shows loading indicator
↓ calls
GetWarehousesUseCase.call()
↓ calls
WarehouseRepository.getWarehouses()
↓ calls
WarehouseRemoteDataSource.getWarehouses()
↓ makes HTTP request
ApiClient.get('/warehouses')
↓ API Response
{
"Value": [...],
"IsSuccess": true,
"IsFailure": false,
"Errors": [],
"ErrorCodes": []
}
↓ parse
List<WarehouseModel> from JSON
↓ convert
List<WarehouseEntity>
↓ wrap
Right(warehouses) or Left(failure)
↓ update state
state = state.setSuccess(warehouses)
↓
UI rebuilds with warehouse list
2. Error Handling Flow
API Error / Network Error
↓
ApiClient throws DioException
↓
_handleDioError() converts to custom exception
↓
ServerException or NetworkException
↓
WarehouseRemoteDataSource catches and rethrows
↓
WarehouseRepositoryImpl catches exception
↓
Converts to Failure:
- ServerException → ServerFailure
- NetworkException → NetworkFailure
↓
Returns Left(failure)
↓
GetWarehousesUseCase returns Left(failure)
↓
WarehouseNotifier receives Left(failure)
↓
state = state.setError(failure.message)
↓
UI shows error state with retry button
3. Warehouse Selection Flow
User taps on WarehouseCard
↓
onTap callback triggered
↓
_onWarehouseSelected(warehouse)
↓
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse)
↓
state = state.setSelectedWarehouse(warehouse)
↓
Navigation: context.push('/operations', extra: warehouse)
↓
OperationSelectionPage receives warehouse
Dependency Graph
┌─────────────────────────────────────────────────┐
│ Riverpod Providers │
├─────────────────────────────────────────────────┤
│ │
│ secureStorageProvider │
│ ↓ │
│ apiClientProvider │
│ ↓ │
│ warehouseRemoteDataSourceProvider │
│ ↓ │
│ warehouseRepositoryProvider │
│ ↓ │
│ getWarehousesUseCaseProvider │
│ ↓ │
│ warehouseProvider (StateNotifierProvider) │
│ ↓ │
│ WarehouseSelectionPage watches this provider │
│ │
└─────────────────────────────────────────────────┘
File Dependencies
warehouse_selection_page.dart
↓ imports
- warehouse_entity.dart
- warehouse_card.dart
- warehouse_provider.dart (via DI setup)
warehouse_card.dart
↓ imports
- warehouse_entity.dart
warehouse_provider.dart
↓ imports
- warehouse_entity.dart
- get_warehouses_usecase.dart
get_warehouses_usecase.dart
↓ imports
- warehouse_entity.dart
- warehouse_repository.dart (interface)
warehouse_repository_impl.dart
↓ imports
- warehouse_entity.dart
- warehouse_repository.dart (interface)
- warehouse_remote_datasource.dart
warehouse_remote_datasource.dart
↓ imports
- warehouse_model.dart
- api_client.dart
- api_response.dart
warehouse_model.dart
↓ imports
- warehouse_entity.dart
State Transitions
┌──────────────┐
│ Initial │
│ isLoading: F │
│ error: null │
│ warehouses:[]│
└──────────────┘
↓
loadWarehouses()
↓
┌──────────────┐
│ Loading │
│ isLoading: T │────────────────┐
│ error: null │ │
│ warehouses:[]│ │
└──────────────┘ │
↓ │
Success Failure
↓ ↓
┌──────────────┐ ┌──────────────┐
│ Success │ │ Error │
│ isLoading: F │ │ isLoading: F │
│ error: null │ │ error: "..." │
│ warehouses:[…]│ │ warehouses:[]│
└──────────────┘ └──────────────┘
↓ ↓
Selection Retry
↓ ↓
┌──────────────┐ (back to Loading)
│ Selected │
│ selected: W │
└──────────────┘
API Response Parsing
Raw API Response (JSON)
↓
{
"Value": [
{
"Id": 1,
"Name": "Warehouse A",
"Code": "001",
...
}
],
"IsSuccess": true,
...
}
↓
ApiResponse.fromJson() parses wrapper
↓
ApiResponse<List<WarehouseModel>> {
value: [WarehouseModel, WarehouseModel, ...],
isSuccess: true,
isFailure: false,
errors: [],
errorCodes: []
}
↓
Check isSuccess
↓
if (isSuccess && value != null)
return value!
else
throw ServerException(errors.first)
↓
List<WarehouseModel>
↓
map((model) => model.toEntity())
↓
List<WarehouseEntity>
Separation of Concerns
Domain Layer
- No dependencies on Flutter, Dio, or other frameworks
- Contains pure business logic
- Defines contracts (repository interfaces)
- Independent and testable
Data Layer
- Implements domain contracts
- Handles external dependencies (API, database)
- Converts between models and entities
- Transforms exceptions to failures
Presentation Layer
- Depends only on domain layer
- Handles UI rendering and user interactions
- Manages local state with Riverpod
- Observes changes and reacts to state updates
Testing Strategy
Unit Tests
├── Domain Layer
│ ├── Test entities (equality, methods)
│ ├── Test use cases (mock repository)
│ └── Verify business logic
├── Data Layer
│ ├── Test models (JSON serialization)
│ ├── Test data sources (mock ApiClient)
│ └── Test repository (mock data source)
└── Presentation Layer
├── Test notifier (mock use case)
└── Test state transitions
Widget Tests
├── Test UI rendering
├── Test user interactions
└── Test state-based UI changes
Integration Tests
├── Test complete flow
└── Test with real dependencies
Benefits of This Architecture
- Testability: Each layer can be tested independently with mocks
- Maintainability: Changes in one layer don't affect others
- Scalability: Easy to add new features following the same pattern
- Reusability: Domain entities and use cases can be reused
- Separation: Clear boundaries between UI, business logic, and data
- Flexibility: Easy to swap implementations (e.g., change API client)
Last Updated: 2025-10-27 Version: 1.0.0