Files
worker/lib/core/network
Phuoc Nguyen fc9b5e967f update perf
2025-12-02 17:32:20 +07:00
..
2025-12-02 17:32:20 +07:00
2025-12-02 17:32:20 +07:00
2025-12-02 17:32:20 +07:00
2025-12-02 17:32:20 +07:00
2025-11-07 11:52:06 +07:00
2025-10-17 17:22:28 +07:00
2025-10-17 17:22:28 +07:00

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

// 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

  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