retry_with_connectivity 1.0.1
retry_with_connectivity: ^1.0.1 copied to clipboard
A reliable execution framework for Flutter that intelligently retries asynchronous operations with backoff strategies and circuit breakers.
retry_with_connectivity (SmartRetry) #
The de facto resilience and reliable execution framework for Flutter and Dart.
Inspired by enterprise resilience libraries like Polly (.NET) and Resilience4j (Java), retry_with_connectivity goes far beyond a simple retry loop. It provides an intelligent, battle-tested framework for building ultra-reliable mobile and desktop applications capable of surviving network outages, server throttling, cascading failures, and offline environments.
🌟 Key Features #
- 🧠 Intelligent Core Engine: Run asynchronous operations safely with
SmartRetry.run()orSmartRetry.runResult(), featuring granular attempt limits, per-attempt timeouts, cumulative duration caps, and cancellation tokens (RetryToken). - 📉 Advanced Backoff Strategies: Production-ready, mathematically sound backoff algorithms designed to prevent thundering herd problems:
ExponentialWithJitterBackoff(Full Jitter, recommended default)DecorrelatedJitterBackoff(Stateful random walks)FibonacciBackoffExponentialBackoffLinearBackoff&FixedBackoffCustomBackoff(Bring your own formula)
- 📡 Dual-Layer Connectivity Awareness:
- Layer 1 (Interface Detection): Monitors hardware network adapters (Wi-Fi, Cellular, Ethernet) via
connectivity_plus. - Layer 2 (Active Reachability): Probes real internet access via DNS lookups (
DnsReachabilityChecker) and TCP sockets (SocketReachabilityChecker) before wasting retry attempts on captive portals or dead Wi-Fi.
- Layer 1 (Interface Detection): Monitors hardware network adapters (Wi-Fi, Cellular, Ethernet) via
- 🌐 HTTP & API Intelligence:
- Automatically inspects status codes (
408,429,500,502,503,504) and enforces idempotency rules (retrying safe methods likeGET,PUT,DELETEwhile protectingPOST). - Parses and respects RFC 7231
Retry-Afterheaders (bothdelay-secondsandHTTP-dateformats) to honor server rate limits.
- Automatically inspects status codes (
- 🔌 First-Class Client Integrations:
- Dio: Drop-in
SmartRetryInterceptorwith per-request overrides viaSmartRetryDioOptions. - package:http: Wrapper
RetryHttpClient(http.BaseClient) with automatic request cloning and stream drainage.
- Dio: Drop-in
- 🛡️ Circuit Breakers: Protect downstream services from cascading failure storms using stateful
CircuitBreaker(closed,open,halfOpen) with configurable failure thresholds, reset timeouts, and probe concurrency control. - 📦 Offline Queue & Automatic Replay: Enqueue critical operations (
QueuedTask) during total disconnects and automatically replay them when connectivity returns usingQueueProcessor. - 📊 Deep Observability: Rich lifecycle callbacks (
onRetry,onSuccess,onFailure,onGiveUp) and built-in structured logging (RetryLogger) for APM integration.
🚀 Installation #
Add retry_with_connectivity to your pubspec.yaml:
dependencies:
retry_with_connectivity: ^1.0.1
# Optional: for Dio integrations
dio: ^5.4.0
# Optional: for package:http integrations
http: ^1.1.0
📖 Quick Start #
1. Simple Retries with Exponential Jitter #
import 'package:retry_with_connectivity/retry_with_connectivity.dart';
Future<void> fetchUserData() async {
// Automatically uses ExponentialWithJitterBackoff (up to 3 attempts)
final data = await SmartRetry.run(
() => apiService.getUserProfile('123'),
policy: RetryPolicy.standard,
);
print('Loaded profile: $data');
}
2. Connectivity-Aware Execution #
When connectivityAware: true is enabled, if the device goes offline mid-retry, the engine pauses backoff timers and waits for dual-layer internet reachability to be restored before counting the attempt!
final result = await SmartRetry.run(
() => syncDatabaseToServer(),
policy: RetryPolicy.aggressive.copyWith(
maxAttempts: 5,
connectivityAware: true, // Pauses during outages and resumes when online
),
callbacks: RetryCallbacks(
onRetry: (attempt, delay, error, context) {
print('Attempt #$attempt failed: $error. Retrying in ${delay.inSeconds}s...');
},
),
);
🌐 Dio & HTTP Client Integrations #
Dio Interceptor #
Add SmartRetryInterceptor directly to your Dio client:
import 'package:dio/dio.dart';
import 'package:retry_with_connectivity/dio.dart';
final dio = Dio()
..interceptors.add(
SmartRetryInterceptor(
policy: RetryPolicy.standard.copyWith(connectivityAware: true),
logger: RetryLogger.console(),
),
);
// Per-request overrides are easily attached via options.extra:
final response = await dio.get(
'https://api.example.com/sensitive-data',
options: SmartRetryDioOptions(
maxAttempts: 5,
perAttemptTimeout: Duration(seconds: 10),
).toOptions(),
);
package:http Client Wrapper #
import 'package:http/http.dart' as http;
import 'package:retry_with_connectivity/http.dart';
final client = RetryHttpClient(
http.Client(),
policy: RetryPolicy.standard.copyWith(maxAttempts: 4),
logger: RetryLogger.console(),
);
final response = await client.get(Uri.parse('https://api.example.com/data'));
🛡️ Circuit Breaker Protection #
Prevent hammering struggling servers using CircuitBreaker:
final breaker = CircuitBreaker(
name: 'payment_gateway_breaker',
config: CircuitBreakerConfig(
failureThreshold: 5, // Trip after 5 consecutive failures
resetTimeout: Duration(seconds: 30), // Stay open for 30s before testing recovery
successThreshold: 1, // Close circuit after 1 successful probe
),
onStateChange: (oldState, newState) {
print('Circuit changed from $oldState to $newState');
},
);
final policy = RetryPolicy(
maxAttempts: 3,
circuitBreaker: breaker,
);
// If the breaker is open, calls fast-fail immediately with CircuitOpenException
// without executing network requests or consuming retry attempts.
await SmartRetry.run(() => paymentClient.processCharge(), policy: policy);
📦 Offline Queue & Replay #
Keep operations safe during extended outages and process them automatically upon reconnection:
// 1. Create the queue and processor
final offlineQueue = InMemoryOfflineQueue();
final processor = QueueProcessor(
queue: offlineQueue,
autoStartOnConnectivityRestored: true, // Auto-replays when online
logger: RetryLogger.console(),
);
processor.start();
// 2. Enqueue tasks when network calls fail or device is disconnected
await offlineQueue.enqueue(
QueuedTask<void>(
id: 'analytics_evt_109',
tag: 'analytics',
action: () => analyticsClient.sendEvent('purchase_completed'),
policy: RetryPolicy.quick,
),
);
// You can also trigger manual replay anytime:
final summary = await processor.process();
print('Queue processed: ${summary.succeeded} succeeded, ${summary.dropped} expired.');
🧩 Advanced Configuration & Presets #
RetryPolicy comes with tuned presets ready for production:
| Preset | Max Attempts | Initial Delay | Max Delay | Backoff Strategy | Connectivity Aware |
|---|---|---|---|---|---|
RetryPolicy.standard |
3 |
1s |
30s |
ExponentialWithJitterBackoff |
false |
RetryPolicy.aggressive |
5 |
500ms |
10s |
ExponentialWithJitterBackoff |
true |
RetryPolicy.conservative |
3 |
2s |
60s |
ExponentialWithJitterBackoff |
true |
RetryPolicy.quick |
2 |
200ms |
2s |
FixedBackoff |
false |
👥 Contributing & License #
We welcome contributions, bug reports, and feature proposals! This project is licensed under the MIT License - see the LICENSE file for details.