retry_with_connectivity 1.0.1 copy "retry_with_connectivity: ^1.0.1" to clipboard
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) #

Pub Version License: MIT Flutter Style

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() or SmartRetry.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)
    • FibonacciBackoff
    • ExponentialBackoff
    • LinearBackoff & FixedBackoff
    • CustomBackoff (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.
  • 🌐 HTTP & API Intelligence:
    • Automatically inspects status codes (408, 429, 500, 502, 503, 504) and enforces idempotency rules (retrying safe methods like GET, PUT, DELETE while protecting POST).
    • Parses and respects RFC 7231 Retry-After headers (both delay-seconds and HTTP-date formats) to honor server rate limits.
  • 🔌 First-Class Client Integrations:
    • Dio: Drop-in SmartRetryInterceptor with per-request overrides via SmartRetryDioOptions.
    • package:http: Wrapper RetryHttpClient (http.BaseClient) with automatic request cloning and stream drainage.
  • 🛡️ 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 using QueueProcessor.
  • 📊 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.

1
likes
160
points
48
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A reliable execution framework for Flutter that intelligently retries asynchronous operations with backoff strategies and circuit breakers.

Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

connectivity_plus, dio, flutter, http

More

Packages that depend on retry_with_connectivity