Attaches an access token to every request and, on a single 401,
refreshes it once and retries the original request. Concurrent 401s
share the same in-flight refresh call instead of each triggering their
own — the classic "10 widgets load at once, token expired" case only
hits your refresh endpoint once.
Coalesces individual load(id) calls made within window into one
call to batchFetcher, then fans the combined result back out to each
caller. Only useful if your backend actually has a batch endpoint
(e.g. GET /users?ids=1,2,3) — this doesn't invent one for you, it
just stops your app from firing N separate requests when N widgets
each ask for one id in the same frame.
Per-key circuit breaker: after failureThreshold consecutive failures
against the same endpoint, stop sending requests to it for cooldown
and fail fast instead of retrying it into the ground — protects a
struggling upstream from a client that keeps hammering it, and
protects the user from a screen that hangs on every retry attempt.
Converts every DioException into one AppError shape, attached as
err.error. Add this LAST in dio.interceptors — errors bubble back
through interceptors in reverse of the order they were added, so
adding this last means it's the first to see the raw exception and
the thing your app-level catch blocks always deal with, regardless
of which upstream or which other interceptor produced it.
In-memory LRU cache. Simple, fast, and gone the moment the app process
dies — that's the correct default for most SWR use (it exists to save
redundant round-trips within a session, not to work offline). Swap in a
persistent SwrCacheStore if you specifically need cold-start data.
In-memory queue — lost on app restart. An offline queue's whole point
is usually to survive exactly that, so treat this as a starting point
and implement MutationQueueStore against Hive/sqflite/
shared_preferences before shipping this to real users.
Queues non-GET requests made while offline and flushes them in order
once connectivity returns. Deliberately has no dependency on any
specific connectivity package — wire isOnline to whatever you
already use (connectivity_plus, an app-level health check, a simple
"last request succeeded" flag, etc) and call flush when it flips
back to true.
Fires several GET requests through the same Dio instance and returns
them together as one logical result. Each request still goes through
SwrInterceptor individually, so each gets its own cache entry, its own
freshness window, and its own background revalidation.
Retries idempotent requests (GET/HEAD/OPTIONS by default) on network
errors or 5xx responses, with exponential backoff, up to maxRetries.
If a circuitBreaker is supplied, a key that keeps failing gets
short-circuited instead of retried indefinitely — see CircuitBreaker.
Storage backend for cached responses. MemoryCacheStore is provided
out of the box; implement this yourself on top of Hive, sqflite, or
shared_preferences if the cache needs to survive app restarts.
Lifecycle events for background revalidation, keyed by cache key.
Listen to SwrInterceptor.events to drive a "refreshing…" indicator,
or to know when to re-read fresh data after a background update lands.
Supplies the current access token and knows how to refresh it.
Implement this against whatever holds your session (secure storage,
an auth SDK, a Bloc/Riverpod provider, etc) — this package doesn't
care where tokens live, only when to ask for one.
method + base url + path + sorted query params. Does NOT factor in
headers, so two requests that differ only by an Authorization header
(e.g. different logged-in users hitting the same path) will collide.
If that applies to you, pass a custom SwrOptions.cacheKey or a custom
CacheKeyBuilder that folds in whatever distinguishes them (e.g. a
user id you already have at call time).
Best-effort default mapping from Dio's exception types to one shape.
Backend error bodies vary per API (some send {"message": ...},
some {"error": {"code": ...}}, some nothing useful at all) — this
can only map what Dio itself already knows (timeout, no connection,
status code). Write your own ErrorMapper that inspects
error.response?.data if you want a real message/code out of your
specific backend's error body; pass it into
ErrorNormalizingInterceptor(mapper: yourMapper).
Thrown by OfflineMutationQueue.send when the request was queued
instead of sent, so calling code can show "saved, will sync" rather
than treating it as either a success or a failure.