Files
unionflow-mobile-apps/lib/core/network/retry_policy.dart
dahoud d094d6db9c Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
2026-03-15 16:30:08 +00:00

161 lines
4.7 KiB
Dart

/// Retry policy with exponential backoff for network requests
library retry_policy;
import 'dart:async';
import 'dart:io';
import 'dart:math';
/// Configuration for retry behavior
class RetryConfig {
/// Maximum number of retry attempts
final int maxAttempts;
/// Initial delay before first retry (milliseconds)
final int initialDelayMs;
/// Maximum delay between retries (milliseconds)
final int maxDelayMs;
/// Multiplier for exponential backoff
final double backoffMultiplier;
/// Whether to add jitter to retry delays
final bool useJitter;
const RetryConfig({
this.maxAttempts = 3,
this.initialDelayMs = 1000,
this.maxDelayMs = 30000,
this.backoffMultiplier = 2.0,
this.useJitter = true,
});
/// Default configuration for standard API calls
static const standard = RetryConfig();
/// Configuration for critical operations
static const critical = RetryConfig(
maxAttempts: 5,
initialDelayMs: 500,
maxDelayMs: 60000,
);
/// Configuration for background sync
static const backgroundSync = RetryConfig(
maxAttempts: 10,
initialDelayMs: 2000,
maxDelayMs: 120000,
);
}
/// Retry policy implementation with exponential backoff
class RetryPolicy {
final RetryConfig config;
final Random _random = Random();
RetryPolicy({RetryConfig? config}) : config = config ?? RetryConfig.standard;
/// Executes an operation with retry logic
///
/// [operation]: The async operation to execute
/// [shouldRetry]: Optional function to determine if error is retryable
/// [onRetry]: Optional callback when retry attempt is made
///
/// Returns the result of the operation
/// Throws the last error if all retries fail
Future<T> execute<T>({
required Future<T> Function() operation,
bool Function(dynamic error)? shouldRetry,
void Function(int attempt, dynamic error, Duration delay)? onRetry,
}) async {
int attempt = 0;
dynamic lastError;
while (attempt < config.maxAttempts) {
try {
return await operation();
} catch (error) {
lastError = error;
attempt++;
// Check if we should retry this error
final retryable = shouldRetry?.call(error) ?? _isRetryableError(error);
if (!retryable || attempt >= config.maxAttempts) {
throw error;
}
// Calculate delay with exponential backoff
final delay = _calculateDelay(attempt);
// Notify about retry
onRetry?.call(attempt, error, delay);
// Wait before next attempt
await Future.delayed(delay);
}
}
// Should never reach here, but throw last error just in case
throw lastError!;
}
/// Calculates delay for given attempt number
Duration _calculateDelay(int attempt) {
// Exponential backoff: initialDelay * (multiplier ^ (attempt - 1))
final exponentialDelay = config.initialDelayMs *
pow(config.backoffMultiplier, attempt - 1).toInt();
// Cap at max delay
var delayMs = min(exponentialDelay, config.maxDelayMs);
// Add jitter to prevent thundering herd
if (config.useJitter) {
final jitter = _random.nextDouble() * 0.3; // ±30% jitter
delayMs = (delayMs * (1 + jitter - 0.15)).toInt();
}
return Duration(milliseconds: delayMs);
}
/// Determines if an error is retryable
bool _isRetryableError(dynamic error) {
// Network errors are retryable
if (error is TimeoutException) return true;
if (error is SocketException) return true;
// HTTP status codes that are retryable
if (error.toString().contains('500')) return true; // Internal Server Error
if (error.toString().contains('502')) return true; // Bad Gateway
if (error.toString().contains('503')) return true; // Service Unavailable
if (error.toString().contains('504')) return true; // Gateway Timeout
if (error.toString().contains('429')) return true; // Too Many Requests
// Client errors (4xx) are generally not retryable
if (error.toString().contains('400')) return false; // Bad Request
if (error.toString().contains('401')) return false; // Unauthorized
if (error.toString().contains('403')) return false; // Forbidden
if (error.toString().contains('404')) return false; // Not Found
// Default: don't retry unknown errors
return false;
}
}
/// Extension to add retry capability to Future operations
extension RetryExtension<T> on Future<T> Function() {
/// Executes this operation with retry logic
Future<T> withRetry({
RetryConfig? config,
bool Function(dynamic error)? shouldRetry,
void Function(int attempt, dynamic error, Duration delay)? onRetry,
}) {
final policy = RetryPolicy(config: config);
return policy.execute(
operation: this,
shouldRetry: shouldRetry,
onRetry: onRetry,
);
}
}