13 KiB
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 interceptorsdioClientProvider- DioClient wrapper instancecacheStoreProvider- Hive-based cache storagecacheOptionsProvider- 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 implementationisConnectedProvider- Current connection statusconnectionTypeProvider- Current connection typenetworkStatusStreamProvider- Stream of status changesNetworkStatusNotifier- 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 messageisCritical- Requires immediate attentioncanRetry- Can be retriedstatusCode- 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
// 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
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
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
// 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
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
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
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:
// For dev environment
static const String baseUrl = devBaseUrl;
// For production
static const String baseUrl = prodBaseUrl;
Timeout Configuration
Adjust timeouts in ApiConstants:
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:
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:
CacheOptions(
store: store,
maxStale: const Duration(days: 7),
hitCacheOnErrorExcept: [401, 403],
priority: CachePriority.high,
allowPostMethod: false,
);
Testing
Connection Testing
// 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
// 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
- Always use DioClient: Don't create raw Dio instances
- Handle specific exceptions: Catch specific error types for better UX
- Check connectivity: Verify network status before critical requests
- Use cache strategically: Cache static data (categories, products)
- Monitor network changes: Listen to connectivity stream for sync
- Clear cache appropriately: Clear on logout, version updates
- Log in debug only: Disable logging in production
- Sanitize sensitive data: Never log passwords, tokens, OTP codes
- Use retry wisely: Don't retry POST/PUT/DELETE by default
- 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:
- Logging (first - logs everything)
- Auth (adds tokens)
- Cache (caches responses)
- Retry (retries failures)
- 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