smart_request 0.2.0
smart_request: ^0.2.0 copied to clipboard
A lightweight Dart package for resilient API calls with retry, backoff, timeout, and fallback.
Smart Request #
Lightweight, dependency-free retries with exponential backoff, timeout, and optional fallback for any async operation.
Works with any HTTP client (Dio, http), GraphQL, gRPC, database calls, or your own async functions.
Features #
- Retry with backoff: exponential factor, max delay, and optional jitter
- Timeout per attempt: wrap each try in a timeout
- Fallback support: switch to an alternate function when retries are exhausted
- Callbacks:
onError,onRetry - Custom retry predicate: decide which errors are transient
- Built-in offline cache manager:
noCache,cacheFirst,cacheAndRefresh
Install #
Add to your pubspec.yaml:
dependencies:
smart_request: ^0.1.0
Quick start (Dio) with cache #
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:smart_request/smart_request.dart';
Future<void> main() async {
final dio = Dio();
final cache = MemoryCacheStore<Response<dynamic>>();
const url = 'https://jsonplaceholder.typicode.com/posts/1';
try {
final response = await smartRequest<Response<dynamic>>(
() => dio.get(url),
fallback: () => dio.get('https://jsonplaceholder.typicode.com/posts/2'),
config: SmartRequestConfig(
maxRetries: 3,
initialDelay: const Duration(milliseconds: 500),
maxDelay: const Duration(seconds: 8),
backoffFactor: 2.0,
jitter: true,
timeout: const Duration(seconds: 5),
onError: (e, s) => print('Error: $e'),
onRetry: (attempt, nextDelay, e, s) =>
print('Retry #$attempt after $nextDelay due to $e'),
shouldRetry: (_) => true,
),
// Scenario 3: return cache and refresh in background
cacheConfig: const CacheConfig(
policy: CachePolicy.cacheAndRefresh,
ttl: Duration(minutes: 10),
),
cacheKey: defaultCacheKeyBuilder(CacheKeyParts(
method: 'GET',
url: url,
query: const {},
headers: const {},
varyHeaders: const ['authorization'],
)),
cacheStore: cache,
onRefresh: (value) {
print('π Background refreshed cache with latest data');
},
);
print('β
First return (cache or network): ${response.data}');
} catch (e) {
print('β Final error: $e');
}
}
API #
typedef RequestFunc<T> = Future<T> Function();
Future<T> smartRequest<T>(
RequestFunc<T> request, {
RequestFunc<T>? fallback,
SmartRequestConfig config = const SmartRequestConfig(),
CacheConfig cacheConfig = const CacheConfig(),
String? cacheKey,
CacheStore<T>? cacheStore,
FutureOr<void> Function(T value)? onRefresh,
});
SmartRequestConfig #
| Field | Type | Default | Description |
|---|---|---|---|
| maxRetries | int | 3 | Number of retries. Total attempts = 1 + maxRetries. |
| initialDelay | Duration | 1s | Delay before the first retry. |
| maxDelay | Duration | 30s | Maximum backoff delay cap. |
| backoffFactor | double | 2.0 | Exponential growth factor for delay. |
| jitter | bool | true | Adds Β±50% randomness to each delay. |
| timeout | Duration | 30s | Per-attempt timeout (applies to fallback too). |
| onError | FutureOr | null | Called on every error before retry decision. |
| onRetry | FutureOr | null | Called before waiting for the next retry. |
| shouldRetry | bool Function(Object error)? | true (all errors) | Decides if an error is retryable. |
| fallbackOn | bool Function(Object error)? | true | Whether to use fallback when retries are exhausted. |
Example app #
See example/lib/main.dart for a runnable example using Dio.
CacheConfig #
| Field | Type | Default | Description |
|---|---|---|---|
| policy | CachePolicy | noCache | Caching strategy: noCache, cacheFirst, cacheAndRefresh. |
| ttl | Duration? | null | Time-to-live for entries. null means never expire. |
Cache keys #
For robust caching you often need more than just the URL. Build a key using:
- method: GET/POST/etc
- url: canonical base URL
- query: sorted query params
- body: canonicalized request payload (for POST/PUT)
- headers: only selected headers (e.g., Authorization) via
varyHeaders
Helper:
final key = defaultCacheKeyBuilder(CacheKeyParts(
method: 'POST',
url: 'https://api.example.com/items',
query: {'page': 1, 'sort': 'asc'},
body: {'name': 'abc', 'tags': ['x', 'y']},
headers: {'authorization': 'Bearer ...', 'accept': 'application/json'},
varyHeaders: ['authorization'],
));