runable
This commit is contained in:
449
lib/core/network/README.md
Normal file
449
lib/core/network/README.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# API Integration Infrastructure - Worker App
|
||||
|
||||
## Overview
|
||||
|
||||
Comprehensive HTTP client infrastructure built with **Dio** and **Riverpod 3.0** for the Worker Flutter application. This setup provides robust API integration with authentication, caching, retry logic, error handling, and offline support.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
lib/core/network/
|
||||
├── dio_client.dart # Main HTTP client with Riverpod providers
|
||||
├── api_interceptor.dart # Authentication, logging, and error interceptors
|
||||
├── network_info.dart # Network connectivity monitoring
|
||||
├── api_constants.dart # API endpoints and configuration
|
||||
├── exceptions.dart # Custom exception definitions
|
||||
└── failures.dart # Domain-level failure types
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Dio HTTP Client (`dio_client.dart`)
|
||||
|
||||
**DioClient Class**
|
||||
- Wrapper around Dio with full method support (GET, POST, PUT, PATCH, DELETE)
|
||||
- File upload with multipart/form-data
|
||||
- File download with progress tracking
|
||||
- Cache management utilities
|
||||
|
||||
**Riverpod Providers**
|
||||
- `dioProvider` - Configured Dio instance with all interceptors
|
||||
- `dioClientProvider` - DioClient wrapper instance
|
||||
- `cacheStoreProvider` - Hive-based cache storage
|
||||
- `cacheOptionsProvider` - Cache configuration
|
||||
|
||||
**Configuration**
|
||||
- Base URL: Configurable per environment (dev/staging/prod)
|
||||
- Timeouts: 30s connection, 30s receive, 30s send
|
||||
- Headers: JSON content-type, Vietnamese language by default
|
||||
- Cache: 7-day max-stale, no caching on auth errors (401, 403)
|
||||
|
||||
### 2. Interceptors (`api_interceptor.dart`)
|
||||
|
||||
#### AuthInterceptor
|
||||
- **Token Injection**: Automatically adds Bearer token to requests
|
||||
- **Token Refresh**: Handles 401 errors with automatic token refresh
|
||||
- **Public Endpoints**: Skips auth for login/OTP/register endpoints
|
||||
- **Language Header**: Adds Vietnamese language preference
|
||||
- **Storage**: Uses SharedPreferences for token persistence
|
||||
|
||||
#### LoggingInterceptor
|
||||
- **Request Logging**: Method, URL, headers, body, query parameters
|
||||
- **Response Logging**: Status code, response data (truncated)
|
||||
- **Error Logging**: Error type, status code, error data
|
||||
- **Security**: Sanitizes sensitive fields (password, OTP, tokens)
|
||||
- **Format**: Beautiful formatted logs with separators
|
||||
|
||||
#### ErrorTransformerInterceptor
|
||||
- **Dio Error Mapping**: Transforms DioException to custom exceptions
|
||||
- **Status Code Handling**:
|
||||
- 400 → ValidationException/BadRequestException
|
||||
- 401 → UnauthorizedException/TokenExpiredException/InvalidOTPException
|
||||
- 403 → ForbiddenException
|
||||
- 404 → NotFoundException
|
||||
- 409 → ConflictException
|
||||
- 422 → ValidationException with field errors
|
||||
- 429 → RateLimitException with retry-after
|
||||
- 5xx → ServerException/ServiceUnavailableException
|
||||
- **Connection Errors**: Timeout, NoInternet, etc.
|
||||
|
||||
#### RetryInterceptor
|
||||
- **Exponential Backoff**: Configurable delay multiplier
|
||||
- **Max Retries**: 3 attempts by default
|
||||
- **Retry Conditions**:
|
||||
- Connection timeout/errors
|
||||
- 5xx server errors (except 501)
|
||||
- 408 Request Timeout
|
||||
- 429 Too Many Requests
|
||||
- **Network Check**: Verifies connectivity before retrying
|
||||
|
||||
### 3. Network Monitoring (`network_info.dart`)
|
||||
|
||||
**NetworkInfo Interface**
|
||||
- Connection status checking
|
||||
- Connection type detection (WiFi, Mobile, Ethernet, etc.)
|
||||
- Real-time connectivity monitoring via Stream
|
||||
|
||||
**NetworkStatus Class**
|
||||
- Connection state (connected/disconnected)
|
||||
- Connection type
|
||||
- Timestamp
|
||||
- Convenience methods (isWiFi, isMobile, isMetered)
|
||||
|
||||
**Riverpod Providers**
|
||||
- `networkInfoProvider` - NetworkInfo implementation
|
||||
- `isConnectedProvider` - Current connection status
|
||||
- `connectionTypeProvider` - Current connection type
|
||||
- `networkStatusStreamProvider` - Stream of status changes
|
||||
- `NetworkStatusNotifier` - Reactive network status state
|
||||
|
||||
### 4. Error Handling
|
||||
|
||||
**Exceptions (`exceptions.dart`)**
|
||||
- NetworkException - Base network error
|
||||
- NoInternetException - No connectivity
|
||||
- TimeoutException - Connection timeout
|
||||
- ServerException - 5xx errors
|
||||
- ServiceUnavailableException - 503 errors
|
||||
- AuthException - Authentication errors (401, 403)
|
||||
- ValidationException - Request validation errors
|
||||
- NotFoundException - 404 errors
|
||||
- ConflictException - 409 errors
|
||||
- RateLimitException - 429 errors
|
||||
- PaymentException - Payment-related errors
|
||||
- CacheException - Cache errors
|
||||
- StorageException - Local storage errors
|
||||
- ParseException - JSON parsing errors
|
||||
|
||||
**Failures (`failures.dart`)**
|
||||
- Immutable Freezed classes for domain-level errors
|
||||
- User-friendly Vietnamese error messages
|
||||
- Properties:
|
||||
- `message` - Display message
|
||||
- `isCritical` - Requires immediate attention
|
||||
- `canRetry` - Can be retried
|
||||
- `statusCode` - HTTP status if available
|
||||
|
||||
### 5. API Constants (`api_constants.dart`)
|
||||
|
||||
**Configuration**
|
||||
- Base URLs (dev, staging, production)
|
||||
- API version prefix (/v1)
|
||||
- Timeout durations (30s)
|
||||
- Retry configuration (3 attempts, exponential backoff)
|
||||
- Cache durations (24h products, 1h profile, 48h categories)
|
||||
- Request headers (JSON, Vietnamese language)
|
||||
|
||||
**Endpoints**
|
||||
- Authentication: /auth/request-otp, /auth/verify-otp, /auth/register, etc.
|
||||
- Loyalty: /loyalty/points, /loyalty/rewards, /loyalty/referral, etc.
|
||||
- Products: /products, /products/search, /categories, etc.
|
||||
- Orders: /orders, /payments, etc.
|
||||
- Projects & Quotes: /projects, /quotes, etc.
|
||||
- Chat: /chat/messages, /ws/chat (WebSocket)
|
||||
- Account: /profile, /addresses, etc.
|
||||
- Promotions & Notifications
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic GET Request
|
||||
|
||||
```dart
|
||||
// Using DioClient with Riverpod
|
||||
final dioClient = ref.watch(dioClientProvider);
|
||||
|
||||
try {
|
||||
final response = await dioClient.get(
|
||||
ApiConstants.getProducts,
|
||||
queryParameters: {'page': '1', 'limit': '20'},
|
||||
);
|
||||
|
||||
final products = response.data;
|
||||
} on NoInternetException catch (e) {
|
||||
// Handle no internet
|
||||
} on ServerException catch (e) {
|
||||
// Handle server error
|
||||
}
|
||||
```
|
||||
|
||||
### POST Request with Authentication
|
||||
|
||||
```dart
|
||||
final dioClient = ref.watch(dioClientProvider);
|
||||
|
||||
try {
|
||||
final response = await dioClient.post(
|
||||
ApiConstants.createOrder,
|
||||
data: {
|
||||
'items': [...],
|
||||
'deliveryAddress': {...},
|
||||
'paymentMethod': 'COD',
|
||||
},
|
||||
);
|
||||
|
||||
final order = Order.fromJson(response.data);
|
||||
} on ValidationException catch (e) {
|
||||
// Handle validation errors
|
||||
print(e.errors); // Map<String, List<String>>
|
||||
}
|
||||
```
|
||||
|
||||
### File Upload
|
||||
|
||||
```dart
|
||||
final dioClient = ref.watch(dioClientProvider);
|
||||
|
||||
final formData = FormData.fromMap({
|
||||
'name': 'John Doe',
|
||||
'avatar': await MultipartFile.fromFile(
|
||||
filePath,
|
||||
filename: 'avatar.jpg',
|
||||
),
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await dioClient.uploadFile(
|
||||
ApiConstants.uploadAvatar,
|
||||
formData: formData,
|
||||
onSendProgress: (sent, total) {
|
||||
print('Upload progress: ${(sent / total * 100).toStringAsFixed(0)}%');
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### Network Status Monitoring
|
||||
|
||||
```dart
|
||||
// Check current connection status
|
||||
final isConnected = await ref.watch(isConnectedProvider.future);
|
||||
|
||||
if (!isConnected) {
|
||||
// Show offline message
|
||||
}
|
||||
|
||||
// Listen to connection changes
|
||||
ref.listen(
|
||||
networkStatusStreamProvider,
|
||||
(previous, next) {
|
||||
next.whenData((status) {
|
||||
if (status.isConnected) {
|
||||
// Back online - sync data
|
||||
} else {
|
||||
// Offline - show message
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### Cache Management
|
||||
|
||||
```dart
|
||||
final dioClient = ref.watch(dioClientProvider);
|
||||
|
||||
// Clear all cache
|
||||
await dioClient.clearCache();
|
||||
|
||||
// Clear specific endpoint cache
|
||||
await dioClient.clearCacheByPath(ApiConstants.getProducts);
|
||||
|
||||
// Force refresh from network
|
||||
final response = await dioClient.get(
|
||||
ApiConstants.getProducts,
|
||||
options: ApiRequestOptions.forceNetwork.toDioOptions(),
|
||||
);
|
||||
|
||||
// Use cache-first strategy
|
||||
final response = await dioClient.get(
|
||||
ApiConstants.getCategories,
|
||||
options: ApiRequestOptions.cached.toDioOptions(),
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Error Handling
|
||||
|
||||
```dart
|
||||
try {
|
||||
final response = await dioClient.post(...);
|
||||
} on ValidationException catch (e) {
|
||||
// Show field-specific errors
|
||||
e.errors?.forEach((field, messages) {
|
||||
print('$field: ${messages.join(", ")}');
|
||||
});
|
||||
} on RateLimitException catch (e) {
|
||||
// Show rate limit message
|
||||
if (e.retryAfter != null) {
|
||||
print('Try again in ${e.retryAfter} seconds');
|
||||
}
|
||||
} on TokenExpiredException catch (e) {
|
||||
// Token refresh failed - redirect to login
|
||||
ref.read(authProvider.notifier).logout();
|
||||
} catch (e) {
|
||||
// Generic error
|
||||
print('Error: $e');
|
||||
}
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
dio: ^5.4.3+1 # HTTP client
|
||||
connectivity_plus: ^6.0.3 # Network monitoring
|
||||
pretty_dio_logger: ^1.3.1 # Request/response logging
|
||||
dio_cache_interceptor: ^3.5.0 # Response caching
|
||||
dio_cache_interceptor_hive_store: ^3.2.2 # Hive storage for cache
|
||||
flutter_riverpod: ^3.0.0 # State management
|
||||
riverpod_annotation: ^3.0.0 # Code generation
|
||||
shared_preferences: ^2.2.3 # Token storage
|
||||
path_provider: ^2.1.3 # Cache directory
|
||||
freezed_annotation: ^3.0.0 # Immutable models
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment-Specific Base URLs
|
||||
|
||||
Update `ApiConstants.baseUrl` based on build flavor:
|
||||
|
||||
```dart
|
||||
// For dev environment
|
||||
static const String baseUrl = devBaseUrl;
|
||||
|
||||
// For production
|
||||
static const String baseUrl = prodBaseUrl;
|
||||
```
|
||||
|
||||
### Timeout Configuration
|
||||
|
||||
Adjust timeouts in `ApiConstants`:
|
||||
|
||||
```dart
|
||||
static const Duration connectionTimeout = Duration(milliseconds: 30000);
|
||||
static const Duration receiveTimeout = Duration(milliseconds: 30000);
|
||||
static const Duration sendTimeout = Duration(milliseconds: 30000);
|
||||
```
|
||||
|
||||
### Retry Configuration
|
||||
|
||||
Customize retry behavior in `ApiConstants`:
|
||||
|
||||
```dart
|
||||
static const int maxRetryAttempts = 3;
|
||||
static const Duration initialRetryDelay = Duration(milliseconds: 1000);
|
||||
static const Duration maxRetryDelay = Duration(milliseconds: 5000);
|
||||
static const double retryDelayMultiplier = 2.0;
|
||||
```
|
||||
|
||||
### Cache Configuration
|
||||
|
||||
Adjust cache settings in `cacheOptionsProvider`:
|
||||
|
||||
```dart
|
||||
CacheOptions(
|
||||
store: store,
|
||||
maxStale: const Duration(days: 7),
|
||||
hitCacheOnErrorExcept: [401, 403],
|
||||
priority: CachePriority.high,
|
||||
allowPostMethod: false,
|
||||
);
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Connection Testing
|
||||
|
||||
```dart
|
||||
// Test network connectivity
|
||||
final networkInfo = ref.watch(networkInfoProvider);
|
||||
final isConnected = await networkInfo.isConnected;
|
||||
final connectionType = await networkInfo.connectionType;
|
||||
|
||||
print('Connected: $isConnected');
|
||||
print('Type: ${connectionType.displayNameVi}');
|
||||
```
|
||||
|
||||
### API Endpoint Testing
|
||||
|
||||
```dart
|
||||
// Test authentication endpoint
|
||||
try {
|
||||
final response = await dioClient.post(
|
||||
ApiConstants.requestOtp,
|
||||
data: {'phone': '+84912345678'},
|
||||
);
|
||||
print('OTP sent successfully');
|
||||
} catch (e) {
|
||||
print('Failed: $e');
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use DioClient**: Don't create raw Dio instances
|
||||
2. **Handle specific exceptions**: Catch specific error types for better UX
|
||||
3. **Check connectivity**: Verify network status before critical requests
|
||||
4. **Use cache strategically**: Cache static data (categories, products)
|
||||
5. **Monitor network changes**: Listen to connectivity stream for sync
|
||||
6. **Clear cache appropriately**: Clear on logout, version updates
|
||||
7. **Log in debug only**: Disable logging in production
|
||||
8. **Sanitize sensitive data**: Never log passwords, tokens, OTP codes
|
||||
9. **Use retry wisely**: Don't retry POST/PUT/DELETE by default
|
||||
10. **Validate responses**: Check response.data structure before parsing
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Offline request queue implementation
|
||||
- [ ] Request deduplication
|
||||
- [ ] GraphQL support
|
||||
- [ ] WebSocket integration for real-time chat
|
||||
- [ ] Certificate pinning for security
|
||||
- [ ] Request compression (gzip)
|
||||
- [ ] Multi-part upload progress
|
||||
- [ ] Background sync when network restored
|
||||
- [ ] Advanced caching strategies (stale-while-revalidate)
|
||||
- [ ] Request cancellation tokens
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Token Refresh Loop
|
||||
|
||||
**Solution**: Check refresh token expiry and clear auth data if expired
|
||||
|
||||
### Issue: Cache Not Working
|
||||
|
||||
**Solution**: Verify CacheStore initialization and directory permissions
|
||||
|
||||
### Issue: Network Detection Fails
|
||||
|
||||
**Solution**: Add required permissions to AndroidManifest.xml and Info.plist
|
||||
|
||||
### Issue: Timeout on Large Files
|
||||
|
||||
**Solution**: Increase timeout or use download with progress callback
|
||||
|
||||
### Issue: Interceptor Order Matters
|
||||
|
||||
**Current Order**:
|
||||
1. Logging (first - logs everything)
|
||||
2. Auth (adds tokens)
|
||||
3. Cache (caches responses)
|
||||
4. Retry (retries failures)
|
||||
5. Error Transformer (last - transforms errors)
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions about the API integration:
|
||||
- Check logs for detailed error information
|
||||
- Verify network connectivity using NetworkInfo
|
||||
- Review interceptor configuration
|
||||
- Check API endpoint constants
|
||||
|
||||
---
|
||||
|
||||
**Generated for Worker App**
|
||||
Version: 1.0.0
|
||||
Last Updated: 2025-10-17
|
||||
Reference in New Issue
Block a user