# 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> } ``` ### 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