527 lines
15 KiB
Markdown
527 lines
15 KiB
Markdown
# Complete App Setup Guide - Warehouse Management App
|
|
|
|
## Overview
|
|
This guide provides a complete overview of the rewritten warehouse management app following clean architecture principles as specified in CLAUDE.md.
|
|
|
|
## App Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Presentation Layer │
|
|
│ (UI, Widgets, State Management with Riverpod) │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ Domain Layer │
|
|
│ (Business Logic, Use Cases, Entities, Interfaces) │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ Data Layer │
|
|
│ (API Client, Models, Data Sources, Repositories) │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ Core Layer │
|
|
│ (Network, Storage, Theme, Constants, Utilities) │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
lib/
|
|
├── core/ # Core infrastructure
|
|
│ ├── constants/
|
|
│ │ ├── api_endpoints.dart # API endpoint constants
|
|
│ │ └── app_constants.dart # App-wide constants
|
|
│ ├── di/
|
|
│ │ └── providers.dart # Riverpod dependency injection
|
|
│ ├── errors/
|
|
│ │ ├── exceptions.dart # Exception classes
|
|
│ │ └── failures.dart # Failure classes for Either
|
|
│ ├── network/
|
|
│ │ ├── api_client.dart # Dio HTTP client with interceptors
|
|
│ │ └── api_response.dart # Generic API response wrapper
|
|
│ ├── router/
|
|
│ │ └── app_router.dart # GoRouter configuration
|
|
│ ├── storage/
|
|
│ │ └── secure_storage.dart # Secure token storage
|
|
│ ├── theme/
|
|
│ │ └── app_theme.dart # Material 3 theme
|
|
│ └── widgets/
|
|
│ ├── custom_button.dart # Reusable button widgets
|
|
│ └── loading_indicator.dart # Loading indicators
|
|
│
|
|
├── features/ # Feature-first organization
|
|
│ ├── auth/ # Authentication feature
|
|
│ │ ├── data/
|
|
│ │ │ ├── datasources/
|
|
│ │ │ │ └── auth_remote_datasource.dart
|
|
│ │ │ ├── models/
|
|
│ │ │ │ ├── login_request_model.dart
|
|
│ │ │ │ └── user_model.dart
|
|
│ │ │ └── repositories/
|
|
│ │ │ └── auth_repository_impl.dart
|
|
│ │ ├── domain/
|
|
│ │ │ ├── entities/
|
|
│ │ │ │ └── user_entity.dart
|
|
│ │ │ ├── repositories/
|
|
│ │ │ │ └── auth_repository.dart
|
|
│ │ │ └── usecases/
|
|
│ │ │ └── login_usecase.dart
|
|
│ │ └── presentation/
|
|
│ │ ├── pages/
|
|
│ │ │ └── login_page.dart
|
|
│ │ ├── providers/
|
|
│ │ │ └── auth_provider.dart
|
|
│ │ └── widgets/
|
|
│ │ └── login_form.dart
|
|
│ │
|
|
│ ├── warehouse/ # Warehouse feature
|
|
│ │ ├── data/
|
|
│ │ │ ├── datasources/
|
|
│ │ │ │ └── warehouse_remote_datasource.dart
|
|
│ │ │ ├── models/
|
|
│ │ │ │ └── warehouse_model.dart
|
|
│ │ │ └── repositories/
|
|
│ │ │ └── warehouse_repository_impl.dart
|
|
│ │ ├── domain/
|
|
│ │ │ ├── entities/
|
|
│ │ │ │ └── warehouse_entity.dart
|
|
│ │ │ ├── repositories/
|
|
│ │ │ │ └── warehouse_repository.dart
|
|
│ │ │ └── usecases/
|
|
│ │ │ └── get_warehouses_usecase.dart
|
|
│ │ └── presentation/
|
|
│ │ ├── pages/
|
|
│ │ │ └── warehouse_selection_page.dart
|
|
│ │ ├── providers/
|
|
│ │ │ └── warehouse_provider.dart
|
|
│ │ └── widgets/
|
|
│ │ └── warehouse_card.dart
|
|
│ │
|
|
│ ├── operation/ # Operation selection feature
|
|
│ │ └── presentation/
|
|
│ │ ├── pages/
|
|
│ │ │ └── operation_selection_page.dart
|
|
│ │ └── widgets/
|
|
│ │ └── operation_card.dart
|
|
│ │
|
|
│ └── products/ # Products feature
|
|
│ ├── data/
|
|
│ │ ├── datasources/
|
|
│ │ │ └── products_remote_datasource.dart
|
|
│ │ ├── models/
|
|
│ │ │ └── product_model.dart
|
|
│ │ └── repositories/
|
|
│ │ └── products_repository_impl.dart
|
|
│ ├── domain/
|
|
│ │ ├── entities/
|
|
│ │ │ └── product_entity.dart
|
|
│ │ ├── repositories/
|
|
│ │ │ └── products_repository.dart
|
|
│ │ └── usecases/
|
|
│ │ └── get_products_usecase.dart
|
|
│ └── presentation/
|
|
│ ├── pages/
|
|
│ │ └── products_page.dart
|
|
│ ├── providers/
|
|
│ │ └── products_provider.dart
|
|
│ └── widgets/
|
|
│ └── product_list_item.dart
|
|
│
|
|
└── main.dart # App entry point
|
|
```
|
|
|
|
## App Flow
|
|
|
|
```
|
|
1. App Start
|
|
↓
|
|
2. Check Authentication (via SecureStorage)
|
|
↓
|
|
├── Not Authenticated → Login Screen
|
|
│ ↓
|
|
│ Enter credentials
|
|
│ ↓
|
|
│ API: POST /auth/login
|
|
│ ↓
|
|
│ Store access token
|
|
│ ↓
|
|
└── Authenticated → Warehouse Selection Screen
|
|
↓
|
|
API: GET /warehouses
|
|
↓
|
|
Select warehouse
|
|
↓
|
|
Operation Selection Screen
|
|
↓
|
|
Choose Import or Export
|
|
↓
|
|
Products List Screen
|
|
↓
|
|
API: GET /products?warehouseId={id}&type={type}
|
|
↓
|
|
Display products
|
|
```
|
|
|
|
## Key Technologies
|
|
|
|
- **Flutter SDK**: >=3.0.0 <4.0.0
|
|
- **State Management**: Riverpod (flutter_riverpod ^2.4.9)
|
|
- **Navigation**: GoRouter (go_router ^13.2.0)
|
|
- **HTTP Client**: Dio (dio ^5.3.2)
|
|
- **Secure Storage**: FlutterSecureStorage (flutter_secure_storage ^9.0.0)
|
|
- **Functional Programming**: Dartz (dartz ^0.10.1)
|
|
- **Value Equality**: Equatable (equatable ^2.0.5)
|
|
|
|
## Setup Instructions
|
|
|
|
### 1. Install Dependencies
|
|
|
|
```bash
|
|
cd /Users/phuocnguyen/Projects/minhthu
|
|
flutter pub get
|
|
```
|
|
|
|
### 2. Configure API Base URL
|
|
|
|
Edit `/Users/phuocnguyen/Projects/minhthu/lib/core/constants/app_constants.dart`:
|
|
|
|
```dart
|
|
static const String apiBaseUrl = 'https://your-api-domain.com';
|
|
```
|
|
|
|
### 3. Configure API Endpoints (if needed)
|
|
|
|
Edit `/Users/phuocnguyen/Projects/minhthu/lib/core/constants/api_endpoints.dart` to match your backend API paths.
|
|
|
|
### 4. Run the App
|
|
|
|
```bash
|
|
flutter run
|
|
```
|
|
|
|
## API Integration
|
|
|
|
### API Response Format
|
|
|
|
All APIs follow this response format:
|
|
|
|
```json
|
|
{
|
|
"Value": <data>,
|
|
"IsSuccess": true,
|
|
"IsFailure": false,
|
|
"Errors": [],
|
|
"ErrorCodes": []
|
|
}
|
|
```
|
|
|
|
### Available APIs
|
|
|
|
#### 1. Login
|
|
```bash
|
|
POST /auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "string",
|
|
"password": "string"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"Value": {
|
|
"userId": "string",
|
|
"username": "string",
|
|
"accessToken": "string",
|
|
"refreshToken": "string"
|
|
},
|
|
"IsSuccess": true,
|
|
"IsFailure": false,
|
|
"Errors": [],
|
|
"ErrorCodes": []
|
|
}
|
|
```
|
|
|
|
#### 2. Get Warehouses
|
|
```bash
|
|
GET /warehouses
|
|
Authorization: Bearer {access_token}
|
|
|
|
Response:
|
|
{
|
|
"Value": [
|
|
{
|
|
"Id": 1,
|
|
"Name": "Kho nguyên vật liệu",
|
|
"Code": "001",
|
|
"Description": null,
|
|
"IsNGWareHouse": false,
|
|
"TotalCount": 8
|
|
}
|
|
],
|
|
"IsSuccess": true,
|
|
"IsFailure": false,
|
|
"Errors": [],
|
|
"ErrorCodes": []
|
|
}
|
|
```
|
|
|
|
#### 3. Get Products
|
|
```bash
|
|
GET /products?warehouseId={id}&type={import/export}
|
|
Authorization: Bearer {access_token}
|
|
|
|
Response:
|
|
{
|
|
"Value": [
|
|
{
|
|
"Id": 11,
|
|
"Name": "Thép 435",
|
|
"Code": "SCM435",
|
|
"FullName": "SCM435 | Thép 435",
|
|
"Weight": 120.00,
|
|
"Pieces": 1320,
|
|
"ConversionRate": 11.00,
|
|
... (43 total fields)
|
|
}
|
|
],
|
|
"IsSuccess": true,
|
|
"IsFailure": false,
|
|
"Errors": [],
|
|
"ErrorCodes": []
|
|
}
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Using Auth Provider
|
|
|
|
```dart
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:minhthu/core/di/providers.dart';
|
|
|
|
class MyWidget extends ConsumerWidget {
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// Watch auth state
|
|
final isAuthenticated = ref.watch(isAuthenticatedProvider);
|
|
final currentUser = ref.watch(currentUserProvider);
|
|
final isLoading = ref.watch(authProvider.select((s) => s.isLoading));
|
|
|
|
// Login
|
|
onLoginPressed() async {
|
|
await ref.read(authProvider.notifier).login(username, password);
|
|
}
|
|
|
|
// Logout
|
|
onLogoutPressed() async {
|
|
await ref.read(authProvider.notifier).logout();
|
|
}
|
|
|
|
return Container();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Using Warehouse Provider
|
|
|
|
```dart
|
|
class WarehousePage extends ConsumerWidget {
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// Watch warehouses
|
|
final warehouses = ref.watch(warehousesListProvider);
|
|
final selectedWarehouse = ref.watch(selectedWarehouseProvider);
|
|
final isLoading = ref.watch(warehouseProvider.select((s) => s.isLoading));
|
|
|
|
// Load warehouses
|
|
useEffect(() {
|
|
Future.microtask(() =>
|
|
ref.read(warehouseProvider.notifier).loadWarehouses()
|
|
);
|
|
return null;
|
|
}, []);
|
|
|
|
// Select warehouse
|
|
onWarehouseTap(warehouse) {
|
|
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
|
context.go('/operations', extra: warehouse);
|
|
}
|
|
|
|
return ListView.builder(...);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Using Products Provider
|
|
|
|
```dart
|
|
class ProductsPage extends ConsumerWidget {
|
|
final int warehouseId;
|
|
final String warehouseName;
|
|
final String operationType;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// Watch products
|
|
final products = ref.watch(productsListProvider);
|
|
final isLoading = ref.watch(productsProvider.select((s) => s.isLoading));
|
|
|
|
// Load products
|
|
useEffect(() {
|
|
Future.microtask(() =>
|
|
ref.read(productsProvider.notifier)
|
|
.loadProducts(warehouseId, warehouseName, operationType)
|
|
);
|
|
return null;
|
|
}, [warehouseId, operationType]);
|
|
|
|
return ListView.builder(...);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Navigation
|
|
|
|
```dart
|
|
// Navigate to login
|
|
context.go('/login');
|
|
|
|
// Navigate to warehouses
|
|
context.go('/warehouses');
|
|
|
|
// Navigate to operations
|
|
context.go('/operations', extra: warehouseEntity);
|
|
|
|
// Navigate to products
|
|
context.go('/products', extra: {
|
|
'warehouse': warehouseEntity,
|
|
'warehouseName': 'Kho nguyên vật liệu',
|
|
'operationType': 'import', // or 'export'
|
|
});
|
|
|
|
// Or use extension methods
|
|
context.goToOperations(warehouse);
|
|
context.goToProducts(warehouse: warehouse, operationType: 'import');
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Using Either for Error Handling
|
|
|
|
```dart
|
|
final result = await ref.read(authProvider.notifier).login(username, password);
|
|
|
|
result.fold(
|
|
(failure) {
|
|
// Handle error
|
|
print('Error: ${failure.message}');
|
|
},
|
|
(user) {
|
|
// Handle success
|
|
print('Logged in as: ${user.username}');
|
|
},
|
|
);
|
|
```
|
|
|
|
### Error Types
|
|
|
|
- **ServerFailure**: API returned an error
|
|
- **NetworkFailure**: Network connectivity issues
|
|
- **AuthenticationFailure**: Authentication/authorization errors
|
|
|
|
## Testing
|
|
|
|
### Run Tests
|
|
|
|
```bash
|
|
flutter test
|
|
```
|
|
|
|
### Run Analysis
|
|
|
|
```bash
|
|
flutter analyze
|
|
```
|
|
|
|
### Test Coverage
|
|
|
|
```bash
|
|
flutter test --coverage
|
|
```
|
|
|
|
## Security Best Practices
|
|
|
|
1. **Token Storage**: Access tokens are stored securely using `flutter_secure_storage` with platform-specific encryption:
|
|
- Android: EncryptedSharedPreferences
|
|
- iOS: Keychain
|
|
|
|
2. **API Security**:
|
|
- All authenticated requests include Bearer token
|
|
- 401 errors automatically clear tokens and redirect to login
|
|
- Sensitive data redacted in logs
|
|
|
|
3. **HTTPS**: All API calls should use HTTPS in production
|
|
|
|
## Performance Optimizations
|
|
|
|
1. **Riverpod**: Minimal rebuilds with fine-grained reactivity
|
|
2. **Lazy Loading**: Providers are created only when needed
|
|
3. **Caching**: SecureStorage caches auth tokens
|
|
4. **Error Boundaries**: Proper error handling prevents crashes
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Login fails with 401
|
|
- Check API base URL in `app_constants.dart`
|
|
- Verify credentials
|
|
- Check network connectivity
|
|
|
|
### Issue: White screen after login
|
|
- Check if router redirect logic is working
|
|
- Verify `SecureStorage.isAuthenticated()` returns true
|
|
- Check console for errors
|
|
|
|
### Issue: Products not loading
|
|
- Verify warehouse is selected
|
|
- Check API endpoint configuration
|
|
- Verify access token is valid
|
|
|
|
### Issue: Build errors
|
|
```bash
|
|
flutter clean
|
|
flutter pub get
|
|
flutter run
|
|
```
|
|
|
|
## Documentation References
|
|
|
|
- **Core Architecture**: `/lib/core/di/README.md`
|
|
- **Auth Feature**: `/lib/features/auth/README.md`
|
|
- **Warehouse Feature**: `/lib/features/warehouse/README.md`
|
|
- **Products Feature**: Inline documentation in code
|
|
- **API Client**: `/lib/core/network/README.md`
|
|
- **Router**: `/lib/core/router/README.md`
|
|
|
|
## Next Steps
|
|
|
|
1. ✅ Core architecture set up
|
|
2. ✅ Auth feature implemented
|
|
3. ✅ Warehouse feature implemented
|
|
4. ✅ Operation selection implemented
|
|
5. ✅ Products feature implemented
|
|
6. ✅ Routing configured
|
|
7. ✅ Dependency injection set up
|
|
8. ⏳ Configure production API URL
|
|
9. ⏳ Test with real API
|
|
10. ⏳ Add additional features as needed
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check inline documentation in code files
|
|
2. Review README files in each module
|
|
3. Check CLAUDE.md for specifications
|
|
|
|
## License
|
|
|
|
This project follows the specifications in CLAUDE.md and is built with clean architecture principles.
|