simple_network_handler 2.0.0
simple_network_handler: ^2.0.0 copied to clipboard
A package for handling network errors with error registry and interceptors. Supports Dio and Supabase.
2.0.0 #
Breaking release — modernized API. See the "Migrating from 1.x" section of the README for the full old→new mapping.
-
BREAKING — static facades removed:
SimpleNetworkHandlerandSupabaseNetworkHandlerare gone. ConstructNetworkHandler(registry)/SupabaseHandler(registry)once at startup and inject them; debug logging and the failure observer are constructor parameters (enableDebugLogging:,onFailure:). No global mutable state remains. -
feat:
RetryInterceptorretries 429 by default — the status that most commonly carries theRetry-Afterheader it honors. -
feat: connectivity-aware retry —
RetryInterceptor.waitForConnectivity: for offline-shaped failures (no response, connectivity exception type) the retry waits for your network gate (e.g. connectivity_plus) instead of backing off blindly; capped bymaxDelayso a dead gate can never hang a request. -
feat: consumer test kits —
package:simple_network_handler/testing.dart(FakeNetworkHandler) andpackage:simple_network_handler_supabase/testing.dart(FakeSupabaseHandler): queueOk/Errresults and unit-test repositories/cubits with no Dio, adapters, or registries; empty queue throws a descriptive error unlessdefaultResultis set. -
feat:
NetworkLogInterceptor— structured, sanitized traffic logging (one line per request and per outcome with duration;authorization/cookie/set-cookie/x-api-keyredacted by default), optional header/body logging with truncation, and a redacted copy-pasteable curl export (logCurl/NetworkLogInterceptor.curl).installNetworkHandling(logging: ...)places it after refresh (final headers) and before retry (every attempt logged). -
BREAKING —
Result<T>replaces dartz'sEither:safeCallreturnsFuture<Result<T>>with sealedOk/Errvariants (exhaustiveswitchsupport).fold(onFailure, onSuccess)is kept with the same argument order, so fold-based call sites migrate by renaming types only. The dartz dependency is gone. -
BREAKING — registry factories return
Failuredirectly:(json) => Left(MyFailure())becomes(json) => MyFailure()(typedefFailureFactory; Supabase:SupabaseFailureFactory). Mapping a response to a substitute success via the registry was removed — perform such recoveries in the repository viaonEndpointError. This closes theEither<Failure, dynamic>/success as Ttype holes entirely.parsedEitherKey→parsedFailureKey. -
BREAKING —
Failureis pure data:getTitle/getSubtitle(BuildContext)moved to the newLocalizedFailuremixin. Migrate each failure withextends Failure with LocalizedFailure(bodies unchanged); generic error UI branches once onfailure is LocalizedFailurewith a fallback.FailureAbstractremoved; the silently-empty default strings are gone. -
BREAKING — Supabase support moved to
simple_network_handler_supabase: importpackage:simple_network_handler_supabase/simple_network_handler_supabase.dartinstead of the old root barrel. The main package no longer depends onsupabase_flutter— Dio-only consumers drop the entire Supabase SDK from their dependency tree. Shared types live insimple_network_handler_core, re-exported by both. -
feat: instance-based
NetworkHandler(registry)andSupabaseHandler(registry)as the primary API — DI-friendly, multiple independently configured backends, isolated tests. -
feat:
Resultgainsmap,flatMap,mapErr,valueOrNull,failureOrNull, andmapBusiness(moved from the removedmap_business_extension.dart, same transport-pass-through semantics). -
feat: Dio-side transport classification —
ErrorRegistrygainsisTransportError(DioException)(default:connectionError/connectionTimeout/sendTimeout/receiveTimeout) andtransportError(defaultgenericError), mirroring the Supabase registry soResult.mapBusinessworks uniformly across both backends. ExplicitdioRegistrymappings always win. -
feat:
onFailureobserver (FailureObserver) onNetworkHandlerandSupabaseHandler— invoked with every handler-produced failure, its original exception and stack trace; one-line Sentry/Crashlytics wiring. A throwing observer never breaks error propagation. -
feat:
RetryInterceptor— exponential backoff with full jitter,Retry-Aftersupport (seconds and HTTP-date, capped atmaxDelay), transient-only conditions (connectivity-shaped exception types + 502/503/504 by default), safe/idempotent methods only by default (opt in per method), cancellation-aware,onRetryobservability hook. Place afterRefreshTokenInterceptor, beforeErrorMappingInterceptor. -
feat: proactive token refresh —
RefreshTokenInterceptor.isTokenExpiredruns a single-flight refresh BEFORE sending when the stored token is known-expired, saving the 401 round-trip; failing proactive refreshes fall back to the reactive path. BundledJwtExpiry.isExpired/expiryOfhelpers (30s default leeway; opaque tokens never assumed expired). -
feat:
RefreshTokenInterceptor.excludedPathsnow supports{param}templates, mid-path*(one segment) and trailing*(any depth); query strings are stripped before matching. -
feat: first-class cancellation —
CancelledFailuremarker + built-inRequestCancelledFailure(core);safeCallmapsDioExceptionType.canceltoErrorRegistry.cancellationError(observer skipped — cancellation is not an error) andResult.mapBusinesslets cancellations pass through like transport failures;CancellationScope+installNetworkHandling(cancellation: scope)enrolls token-less requests soscope.cancelAll()aborts everything in flight (sign-out), reusable immediately, per-call tokens take precedence. Per-page cancellation via ambient scopes:pageScope.bind(() => ...)enrolls every request started inside the block (zone-propagated through the whole await chain — no token plumbing); the scope-enrollment interceptor is always installed byinstallNetworkHandling, sobindworks without a Dio-wide scope. Precedence: per-call token → ambient scope → Dio-wide scope. Newsimple_network_handler_blocpackage:CancellableRequestsmixin ties cancellation to the bloc/cubit lifecycle (cancellable(() async {...})+ automaticcancelAllon close). -
feat:
dio.installNetworkHandling(errorRegistry: ..., refresh: ..., retry: ...)— one-call setup that owns the interceptor ordering (refresh → retry → mapping) so it can't be wired wrong, and injects the retry client.RetryInterceptor.diois now optional at construction (asserted when hand-wiring without one). -
chore: GitHub Actions CI — analyze + test for all three packages and the example app.
1.4.0 #
- fix: endpoint registry keys with
{param}templates (e.g./api/users/{id}) now match real request paths (/api/users/5). Previously template keys NEVER matched (Retrofit substitutes params before the request), so those mappings silently fell through to*/genericError. Lookup precedence: exact path (query string stripped) → template match (declaration order) →*. Behavioral note: registries already containing template keys will start firing their specific mappings — this is the documented intent, but review yours if you relied on the fall-through. - fix: calling
safeCallwithout a configured registry now throws a descriptiveStateErrorin ALL build modes. Previously this was anassert, so release builds crashed on a bare null-check instead. - fix: a registered factory producing a success value of the wrong type no longer throws a
TypeErrorout ofsafeCall(breaking theEithercontract) — it is reported viaonMappingErrorand falls back togenericError. - feat:
ErrorRegistry.onMappingError(error, stackTrace, response)— invoked when an endpoint factory throws (e.g. a brokenfromJson) or produces a mis-typed success. Default implementation logs a prominent warning in debug builds (previously these were swallowed silently); override to report to your error tracker. - feat: predicate-based exception matching via
ExceptionMatcherand the newgeneralMatcherslist on bothErrorRegistryandSupabaseErrorRegistry. The existinggeneralRegistrymaps match only the exactruntimeType(subclasses never match); matchers supportis-checks:ExceptionMatcher.whenType<MyBaseException>((e) => MyFailure()). - feat:
SimpleNetworkHandler.reset()andSupabaseNetworkHandler.reset()(@visibleForTesting) so tests no longer share registry state. - docs:
ErrorMappingInterceptornow documents that mapped 2xx statuses produce a synthetic rejection that intentionally bypasses other interceptors'onError.
1.3.0 #
- feat: add
TransportFailuremarker mixin on the sharedFailurebase. Mix it onto any failure representing a transport-level problem (offline / timeout / unreachable) so UIs and observers can special-case connectivity once, regardless of feature. Because it lives on the shared base, it is available to both the Dio (REST) and Supabase variants. - feat:
SupabaseErrorRegistrygains first-class transport classification —bool isTransportError(Object error)(default coversTimeoutExceptionandSocketException;SocketExceptionmatched by runtime type name to avoid adart:ioimport so web builds keep compiling) andFailure get transportError(defaults togenericError; override to return your localized offline failure). Wired intosafeCall's general-exception path. - feat: add
Either<Failure, T>.mapBusiness(ifBusinessError, onData)extension — a transport-aware fold that letsTransportFailures pass through unchanged while substituting a feature-specific failure for any otherLeft. Replaces thefold((_) => Left(FeatureFailure()), ...)pattern that discarded transport classification. - note: fully backward compatible. Existing consumers compile and behave identically without opting in — the default
transportErrorisgenericError, and the new transport check runs only AFTER the existinghandleRealtimeErrorconsultation, so registries that classify transport errors via the realtime hook keep working unchanged. - migration hint: replace
result.fold((_) => Left(FeatureFailure()), (data) => Right(...))withresult.mapBusiness(const FeatureFailure(), (data) => Right(...)), and overridetransportErroron your registry to return a failure that mixes inTransportFailure.
1.2.0 #
- feat: add RefreshTokenInterceptor with automatic token refresh, single-flight queuing of concurrent 401s and request replay
- feat: add TokenStore interface and RefreshRequest spec executed on a separate bare Dio instance
- docs: document the required interceptor order with ErrorMappingInterceptor
1.1.1 #
- fix:
SupabaseNetworkHandlernow resolvesAuthExceptions against the auth registry by semanticcode(e.g.email_not_confirmed,invalid_credentials), falling back tostatusCodeand thenmessage. Previously onlystatusCode/messagewere checked, so registries keyed by error codes never matched.
1.1.0 #
- feat: add Supabase API support with
SupabaseNetworkHandlerandSupabaseErrorRegistry - feat: add Supabase-specific failure classes (AuthFailure, PostgrestFailure, StorageFailure, etc.)
- feat: add separate import for Supabase:
import 'package:simple_network_handler/simple_network_handler_supabase.dart' - feat: add example Supabase error registry and repository implementation
1.0.4 #
- feat: change general error to accept types and provide access to the exception itself
- feat: add logging functionality
1.0.3 #
- feat: add handling of custom exceptions
1.0.2 #
- chore: update readme
1.0.1 #
- chore: update readme
- feat: add example app that showcases an advanced use case
1.0.0 #
- feat: initial release of Simple Network Handler