NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Production-ready Flutter/Dart API client with auth refresh queue, exponential retry, smart caching and error tracking (Sentry-ready). Powered by Dio.
Last release 13 days ago
24 Sep 2026
Ships fairly regularly
a new release about every 4 weeks
Nearly every release is documented
notes for 16 of 17 stable releases
Nothing withdrawn
no release was ever pulled
6 months old
17 releases · first in 2026
One column per month.
flutter_secure_storage 11 is accepted (>=10.0.0 <12.0.0). Pub picks it on Flutter ≥ 3.38 only; it needs Android minSdk 24 and iOS 13, and it reads And
flutter_secure_storage 11 is accepted (>=10.0.0 <12.0.0). Pub picks it
on Flutter ≥ 3.38 only; it needs Android minSdk 24 and iOS 13, and it reads
Android data still in EncryptedSharedPreferences — written through apix ≤
1.1.0 and never opened on plugin 10 since — as absent:
checkUpgradeStatus() on your own FlutterSecureStorage reports it.storeUnusable): the README has the backup rules that prevent it, and the
recovery.SecureStorageService says which plugin versions each behaviour holds for;
on Android 11 a withBiometrics() refusal arrives wrapped from the first call.HttpException.responseBody is the decoded JSON when a JSON error body arrived as bytes or text; the raw value stays on originalError.response.data. If
HttpException.responseBody is the decoded JSON when a JSON error body
arrived as bytes or text; the raw value stays on
originalError.response.data. If you decoded it yourself, drop that step.getAndReadBytes — and post/put/patch/deleteAndReadBytes — return
a BinaryResponse: the bytes with their status and headers, contentType
and a sanitised fileName, checked against expectedContentTypes if given.RequestMetrics.copyWith takes requestSize.RequestMetrics.requestSize and responseSize are byte counts, taken
without rendering the body — they were the length of its toString().401 refreshes again), a 2xx raises
ParsingException — raw verbs included — instead of
ApiException('Unknown error').message and code whatever the
responseType; one over 64 KB is left as received.Uint8List prints <binary: N bytes>.DioException: a repeated
Retry-After keeps a 429 typed and waits the longest delay; repeated
Cache-Control lines combine.SecureStorageService no longer deletes when the store's own key is unusable — it rethrows. Its message can contain Bad padding (Key mismatch after alg
SecureStorageService no longer deletes when the store's own key is unusable
— it rethrows. Its message can contain Bad padding
(Key mismatch after algorithm change (Bad padding, …)) and used to be read
as a corrupted entry, taking a deletion the plugin cannot run: you got a purge
report for a purge that never happened, and the cleanup's exception instead of
the one naming the cause.onBeforeRecoveryDelete announces an imminent attempt, not an
accomplished deletion. If the deletion fails, the original error is rethrown.SecureStorageService.classify and SecureStorageFailure —
unreadableEntry / storeUnusable / other. Tells a lost entry from a store
that needs a retry, without matching platform strings on your side.IllegalBlockSizeException, WRONG_FINAL_BLOCK_LENGTH and error:1e00007b
now count as an unreadable entry, alongside their BadPaddingException
sibling.storeUnusable: retry once, and treat a second
failure as permanent.resetOnError: true has to share
apix's store, so it governs apix's calls for the rest of the process.withBiometrics() on an unsecured device raises the bare
BIOMETRIC_UNAVAILABLE only on the first run; afterwards it arrives
wrapped in Migration failed after algorithm change …. Keying on the bare
string sees it once and never again.example/README.md
tagged its entries with the release that introduced them and had stopped at
v2.3.0 — the tags are gone, and a guard keeps them gone.Two audits of the package, then the consumer's review of both: 47 defects, 20 reproduced against the real client. None raised, logged or reddened a te
Two audits of the package, then the consumer's review of both: 47 defects, 20 reproduced against the real client. None raised, logged or reddened a test.
Nothing breaks your build — no public symbol removed or narrowed, checked by
a 4.1.0-era consumer compiled against this release
(test/migration_4_1_0_compiles_test.dart). Everything below is runtime.
Four defaults used to send personal data to a third party out of the box.
SentrySetupOptions.sendDefaultPii → false. It was true, against the
SDK's own default: an app that configured nothing shipped request headers,
cookies and the user's IP to the tracker.ErrorTrackingConfig.captureResponseBody → false.ErrorTrackingConfig.redactUrls → true: query values are replaced
before a URL reaches the tracker. Names are kept.LoggerConfig logs no request or response body, error path included.
LoggerConfig.trace() still keeps both.⚠️ The middle two remove a field you may be diagnosing from. Set them back deliberately rather than discovering thinner Sentry tickets.
CacheConfig.varyHeaders, default
['Authorization']) and keyed on the request body. A token refresh is now a
miss — vary on a stable identity header, or const [] to opt out.CacheStorage.keys() no longer deletes expired entries — use
CacheInterceptor.evictExpired().SentrySetup.init starts the app without Sentry, not at all.SecureStorageService stops the Android plugin repairing unreadable entries
behind it (resetOnError: false). Under the plugin's default the read still
answered null — after destroying a credential without onBeforeRecoveryDelete
ever firing. A failed initialisation now raises instead of resetting: inject
your own FlutterSecureStorage to keep the old behaviour.responseValidator rejections, cacheOnly
misses. Real failures, not new noise. A filter keyed on exception names
cannot tell them from their dart:io namesakes.onSendProgress / onReceiveProgress on those methods.ApiClient.cacheInterceptor — the invalidation API on the instance in use.CacheConfig.onCacheHit / onCacheError — a hit reaches no observer, and a
storage failure was absorbed in silence.CacheInterceptor.evictExpired().SecureStorageService.onBeforeRecoveryDelete — fires before the recovery
deletes a credential.RequestOptions.forceRevalidate(), MultipartReplayException,
CacheBodyEncoding, ErrorTrackingConfig.redactUrls.Cache
/users?page=1 and ?page=2 shared one entry.text/plain 12345 came back an int.304 served the body as stale and never restarted the TTL.no-cache and must-revalidate were parsed and ignored.invalidateUrl('/users') also removed /users-archived and /users/123.clearCache() under-reported by the entries keys() had just swept.FileCacheStorage writes to one key raced on a shared temp file.Multipart
{'a': {'b': {'file': File}}} sent an empty body, and returned 200.StateError replaced the server's status.Observability
responseValidator rejection was recorded as a success, cached, then served unvalidated.logResponseBody said.ErrorMapperInterceptor rewrote an already-typed exception.RequestOptions was observed once; every retry stranded a metric.profilesSampleRate and both replay rates were accepted and ignored;
customBeforeSendTransaction got an empty Hint.Client
…OrEmpty / …OrNull variants broke on a bare []. They tolerate every
shape of empty now, including an absent data key; the strict ones name the
key they wanted.ApiException.code returned the HTTP status disguised as a business code.RetryConfig broke the == / hashCode contract.RetryInterceptor swallowed failures of its own retry machinery.TokenProviderException.onAuthFailure froze the refresh queue.extra: {'cacheTtl': …} and extra: {'forceRefresh': true},
which never existed in the code. Use defaultTtl and forceRevalidate().ApiClientConfig.interceptors sees a raw DioException;
TokenProviderOperation.clear is never raised by apix.varyHeaders, forceRevalidate(), MultipartReplayException and
CacheBodyEncoding are documented at last.SecureStorageService.withBiometrics() refuses on a device with no PIN,
pattern, password or enrolled biometric — it does not degrade silently, as
this file previously claimed. Do not catch that refusal and keep writing: the
plugin is left without a cipher for the rest of the process.Two rounds of the same defect, reported by a consumer and then found by auditing for others like it: a Future completed with an error that nobody list
Two rounds of the same defect, reported by a consumer and then found by
auditing for others like it: a Future completed with an error that nobody
listens to is reported to the zone — a Sentry event nobody can act on.
RequestDeduplicator no longer reports an unhandled error on every failed
request. Its Completer exists for duplicates that usually never arrive,
so nothing listens to it — harmless on success, but completeError with no
listener reports to the zone. Invisible until a CancelToken made
cancellation routine. DeduplicationInterceptor, added in 4.0.0, shares that
deduplicator, so the defect had just become reachable without a cache.ErrorTrackingConfig.onError no longer leaks the same way. Its Future
was neither awaited nor ignored, so a tracker failing asynchronously reported
an error nobody could receive — from inside the component whose job is
receiving errors.logHandler, onMetrics, onBreadcrumb or span starter that threw turned a
200 into an ApiException — an analytics backend having a bad minute
failed the business request it was only observing.CancelToken, so a cancellation put the outer one through the error chain
twice: one network call, two log lines, two tracker events.EncryptedCacheStorage.has() can delete (it decrypts to answer, and purges
what it cannot open), AuthInterceptor swallows a throwing onAuthFailure
to avoid deadlocking the refresh queue, and TooManyRequestsException .retryAfter is populated regardless of RetryConfig.respectRetryAfter.This release answers an integration report from a consumer, filed after moving a production app onto 3.0.0. Eight gaps, none of which broke a build —
This release answers an integration report from a consumer, filed after moving a production app onto 3.0.0. Eight gaps, none of which broke a build — three of them were only findable by reading apix's source, which is why they survived the last release.
The theme is the same throughout: apix knew something the consumer could not
reach. An error code it read past, a Retry-After it parsed and dropped, a
duration it measured and never reported.
| If you… | Then… |
|---|---|
catch on ClientException for a 429 |
still works — TooManyRequestsException is a subtype. Read retryAfter to say how long to wait |
| assert an exact retry delay in a test | add jitter: 0 — delays are now spread ±20 % by default |
rely on CacheStrategy.networkOnly writing to your store |
it no longer writes. If you were counting on that, use networkFirst |
wrote a no-op CacheStorage just to get deduplication |
delete it — pass deduplicationConfig and no cacheConfig |
group Sentry issues by the type passed to onError |
the onResponse path now sends HttpTrackingException as an HttpException. Existing issues may regroup |
import 'package:dio/dio.dart' for ResponseType, Interceptor, FormData… |
import package:apix/apix.dart instead; adapters live in package:apix/testing.dart |
subclass ApiException with your own code field |
remove it and pass super.code — a same-typed field now shadows the inherited one, and a differently-typed one (int code) will not compile |
| use none of the above | nothing to do |
Retry delays are no longer deterministic. RetryConfig.jitter defaults to
0.2, spreading each computed backoff across ±20 % of itself.
jitter: 0.0 restores the previous sequence exactly. Any test asserting a
precise delay needs it.Retry-After is never jittered.CacheStrategy.networkOnly no longer writes to the store. It always
documented "never read cache"; only the reading half was enforced, so every
response was still written — personal data included — through a store
nobody ever read from.
onResponse and the
deduplicated path — and a guard on the first alone still leaked on exactly
the path a consumer enabling deduplication would take.ApiException gained a code field, which collides with any subclass
that already declared one. Same type (String): the subclass shadows the
inherited field and the analyzer warns. Different type (int code): it no
longer compiles. Drop the field and forward super.code instead — this was
found by rebuilding apix's own example app against 4.0.0, not by reading.
HttpTrackingException now extends HttpException. The type handed to
ErrorTrackingConfig.onError changes on the onResponse path, so trackers
that group by runtime type may regroup existing issues.
ApiException.code — the application error code, read from the response
body under errorCodeKey (default 'code', configurable like dataKey).
Branch on a stable business code instead of an HTTP status that drifts from
400 to 409 to 422 across server revisions. Always a String, even when
the server sends a number; null on failures that have no body.
TooManyRequestsException — a 429 with its parsed retryAfter, so the
user can be told how long to wait instead of receiving the same generic
message as for a malformed request.
DeduplicationConfig / DeduplicationInterceptor — deduplication without
a cache. RequestDeduplicator was only ever instantiated by
CacheInterceptor, so getting one meant installing the other. When both are
configured, the cache's own deduplication is switched off rather than
collapsing each request twice.
EncryptedCacheStorage — a decorator that seals body and headers before
they reach any CacheStorage. You supply encrypt/decrypt, so apix takes
on neither a crypto dependency nor your key. Cache keys stay in clear text —
the invalidation API reads them — so keep identifiers out of URLs you cache.
An entry that cannot be decrypted reads as a miss and is purged.
TracingInterceptor / TracingConfig — one performance span per request,
as a child of the current Sentry transaction. apix already measured duration,
size and status; nothing ever opened a span, so durations could only land in a
breadcrumb: visible after an incident, never aggregated.
RetryInterceptor.onRetry — fires before each retry waits, carrying the
attempt number, the delay and the cause. A retry storm used to be invisible:
only the final failure surfaced.
package:apix/testing.dart — HttpClientAdapter, ResponseBody and
friends, so stubbing an adapter no longer forces a direct dio import (and with
it, apix's dio version range) onto your test suite.
The dio barrel now also re-exports ResponseType, RequestOptions,
Interceptor and its handlers, DioException, DioExceptionType,
FormData, MultipartFile and Headers — derived from where apix's own API
hands a dio type to a consumer. create(interceptors:) took a
List<Interceptor> that could not be written without importing dio; binary
downloads had no way to name ResponseType.
Retry-After parsing moved to a shared helper used by both the retry
interceptor and the error mapper, so what the caller is told to wait and what
the interceptor actually waits cannot drift apart.
The onResponse error-tracking path now attaches a stack trace. It had none,
while the onError path always did.
This release is about the cache. Two of its promises were not kept: cacheFirst did not refresh in the background despite saying so, and the TTL was on
This release is about the cache. Two of its promises were not kept: cacheFirst
did not refresh in the background despite saying so, and the TTL was only as
strong as whichever CacheStorage you plugged in. Both are now true. Error
tracking also stops flattening every failure into one DioException.
| If you… | Then… |
|---|---|
use CacheStrategy.cacheFirst |
responses may now be older than defaultTtl. Check response.isStale, or move to networkFirst where freshness matters |
call CacheRequestExtension.isFromCache(response) |
replace with response.isFromCache |
implement CacheStorage yourself |
delete the expiry filter from get — keep it in has |
inspect the argument of ErrorTrackingConfig.onError |
it is the typed ApiException now, not a DioException. e is DioException stops matching silently — use (e as ApiException).originalError |
catch on ApiException around a cacheOnly call |
you will now actually catch CacheException; before, it slipped through |
catch on HttpException for 4xx/5xx |
still works — ClientException / ServerException are subtypes |
use SentrySetup with filterNetworkNoise |
your 5xx start reaching Sentry. Expect more events, not fewer — they were being dropped |
use nothing but networkFirst (the default) |
nothing to do |
CacheStrategy.cacheFirst now serves stale data — it does what it always
documented: serve the cache immediately, refresh behind
(stale-while-revalidate). An expired entry is returned, flagged isStale,
while one background request renews it.
defaultTtl. Where
that is unacceptable — a stock level, a quota, an authorisation — use
networkFirst, or surface response.isStale.ErrorTrackingConfig.onError receives the typed ApiException, not the
raw DioException. Trackers group by runtime type, so a 500, a 404 and a
timeout used to land in a single issue titled DioException. Nothing fails to
compile; e is DioException just stops matching — reach the original through
(e as ApiException).originalError.
CacheRequestExtension.isFromCache(response) removed — use
response.isFromCache. The old form was a static on an extension of
RequestOptions taking a Response, so autocomplete never surfaced it.
CacheStorage.get must no longer filter on expiry — only affects custom
implementations. It returns entries expired or not; null means absent. The
interceptor owns the TTL, so a backend can no longer weaken it by forgetting
to filter. has keeps filtering.
response.isFromCache / response.isStale — isStale is true wherever
apix knowingly returns expired data: cacheFirst revalidating, and the
offline fallback of networkFirst / httpCacheAware. On a stock level, a
quota or a status, surface it. A 304 is not stale. The underlying keys
are exported as fromCacheKey / fromCacheStaleKey.
FileCacheStorage — a cache that survives restarts, with no new
dependency: one JSON file per entry, in a directory you supply.
maxEntries: 200) — a process cache dies with the
app, a disk cache does not. Expired entries are evicted first.
maxEntries: null opts out.ClientException and ServerException were never thrown — ErrorMapper
specialised only 401/403/404 and mapped everything else to a bare
HttpException, leaving both clauses dead at every call site despite the
documented hierarchy. Unspecialised 4xx now map to ClientException, 5xx to
ServerException; other statuses stay HttpException. Not breaking — both
are HttpException subtypes.
ApiX errors were discarded by ApiX's own Sentry filter — SentryException.type
is a bare class name, so the noise filter could not tell apix's
HttpException from dart:io's and dropped every 5xx. Classification is
now by type hierarchy. Name matching is unchanged for everything else.
CacheException was not an ApiException — a cacheOnly miss escaped
the typed-error contract entirely, because RequestInterceptorHandler.reject
skips the following error interceptors.
cacheOnly served expired entries — it now rejects them, distinguishing a
miss from an expiry.
Cache eviction ignored expiry — InMemoryCacheStorage could drop a fresh
entry while keeping a stale one.
⚠️ BREAKING BEHAVIOR — Retry is now HTTP-method-aware; `POST` and `PATCH` are no longer retried by default
POST and PATCH are no longer retried by default
retryStatusCodes, ignoring the HTTP method. A 5xx returned after the server had already committed (e.g. a gateway 502/504 timeout following an order) would replay a non-idempotent request and produce a duplicate (the same order placed twice).RetryInterceptor now retries only requests whose method is in the new RetryConfig.retryableMethods, which defaults to the idempotent methods per RFC 7231 §4.2.2: {GET, HEAD, OPTIONS, TRACE, PUT, DELETE}. Method matching is case-insensitive.POST/PATCH being retried, either widen the set globally with RetryConfig(retryableMethods: {...'POST'}), or opt in per request (recommended) with RequestOptions.forceRetry() — see below.statusCode == null), Retry-After handling, and the per-request disableRetry() opt-out (still takes precedence).RetryConfig.retryableMethods — Set<String> of upper-case HTTP methods eligible for retry (default = idempotent methods)
RequestOptions.forceRetry() — per-request opt-in to retry a non-idempotent method
POST/PATCH protected by an Idempotency-Key.maxAttempts; disableRetry() still wins if both are set.disableRetry(); backed by the exported forceRetryKey extra.Dio Options, CancelToken and Response re-exported from the apix barrel — no more direct package:dio import for common calls (Options(extra: {noRetryKey: true}), cancelToken:, typing a returned Response<T>, ...)
>=5.4.0 <7.0.0 range — dio 5.10.0 introduced the DioExceptionType.transformTimeout enum value (breaking the exhaustive exception-mapping switches) and an optional parameter on ErrorInterceptorHandler.reject. Exception mapping now routes transformTimeout — and any future DioExceptionType — through its default branch (ErrorMapperInterceptor maps it to a generic ApiException; AuthInterceptor treats it as a non-network failure), so apix builds on both the floor and the latest of its declared dio range.`SentrySetupOptions.configureOptions` — Escape hatch for SentryFlutterOptions not exposed by apix
SentrySetupOptions.configureOptions — Escape hatch for SentryFlutterOptions not exposed by apix
void Function(SentryFlutterOptions) invoked last during SentryFlutter.init, after every apix defaultenableTombstone in sentry_flutter 9.14+, enableAppHangTrackingV2, replay tuning)beforeSend / beforeSendTransaction — for composition that preserves apix's network-noise filter, prefer customBeforeSend / customBeforeSendTransaction*AndDecode / *AndParse — Parsing failures now surface as typed ApiException (critical)
*AndDecode / *AndParse — Parsing failures now surface as typed ApiException (critical)
ParsingException (extends ApiException) thrown when fromJson or a custom parser callback throws (e.g. truncated JSON, type mismatch)on ApiException catch now catches every client-side parse failureoriginalError and stackTrace are preservedApiException from inside fromJson/parser is rethrown unchanged (no double-wrap)AuthInterceptor — Network blip no longer logs the user out (major)
NetworkException (typed: ConnectionException, TimeoutException)onAuthFailure is not invoked on network failuresAuthException and call onAuthFailure (regression preserved)AuthInterceptor — Token provider failures now typed (moderate)
TokenProviderException(operation: read | write | clear) (extends ApiException)getAccessToken, getRefreshToken, and the user-supplied onTokenRefreshed callbackon TokenProviderException catch distinguishes credential storage issues from network/HTTP errorsAuthException now preserves the underlying cause
AuthException(message, originalError: ..., stackTrace: ...) — typed cause flows through to the caller via originalErrorRetryConfig.respectRetryAfter — Honor the server's Retry-After header on retryable responses (default true)
"60") and HTTP-date ("Wed, 21 Oct 2026 07:28:00 GMT") formats per RFC 7231 §7.1.3RetryConfig.maxDelayMs; falls back to exponential backoff if the header is absent or malformedRetryInterceptor.parseRetryAfter(value, {now}) exposed for advanced use and testingApiClientConfig.strictContentType — Detect captive portals / wrong Content-Type (default false)
true, *AndDecode methods verify the response's Content-Type starts with application/jsonUnexpectedContentTypeException (extends ApiException) on mismatch — exposes expectedContentType and actualContentType fields*AndParse methods are unaffected (they accept any payload type by design)ApiClientConfig.responseValidator — Hook for legacy APIs that signal business errors via HTTP 200
ResponseValidator typedef: ApiException? Function(Response)null lets the response pass throughErrorMapperInterceptor)BusinessException)*AndDecode methods now use <dynamic> internally with explicit _requireData validation (instead of relying on Dio's eager generic cast)
TypeError on non-JSON responses; replaced by clear ApiException messages_requireData now also throws ApiException when the body is non-null but not a Map<String, dynamic>`ApiClient` methods now throw `ApiException` instead of `DioException`
ApiClient methods now throw ApiException instead of DioException
get, post, put, delete, patch) and typed variants unwrap DioException automaticallyon DioException catch on ApiClient methods must migrate to on ApiException catch (or subtypes)client.dio (raw Dio access) still throws DioException — only ApiClient methods are affectedgetResult() handles both ApiException and DioException (fallback for raw Dio usage)AuthInterceptor — Refresh request isolation (critical)
ApiClient methods now throw typed ApiException directly (critical)
get, post, put, delete, patch) unwrap DioException automaticallyon ClientException catch, on UnauthorizedException catch, etc. now work as expectedDioException and extract .error manuallygetResult() also works correctly with both ApiException and DioException (fallback for raw Dio usage)CacheInterceptor — Cache key generation (critical)
invalidateUrl() now resolves relative URLs against the client's base URLCacheInterceptor — Deduplicated requests (major)
ApiClient — Null safety on response body (major)
*AndDecode methods now throw ApiException instead of TypeError on null response body (e.g. 204 No Content)_extractData now throws ApiException instead of TypeError on non-Map responsesErrorMapperInterceptor — Nested error message extraction (major)
{ "error": { "message": "..." } }error.message, error.detail, error.descriptioncaptureStatusCodes filter now applies consistently in onError (previously only filtered in onResponse)MetricsInterceptor — Request ID collisions (moderate)
_inFlight.length for unique IDsSecureStorageService — Corruption blast radius (moderate)
read() and containsKey() now delete only the corrupted key instead of calling deleteAll()Interceptor resilience (moderate)
AuthInterceptor, RetryInterceptor, CacheInterceptor now wrap async onRequest/onError/onResponse in try/catch to prevent silent request hangs on unexpected exceptionsSentry noise filter (minor)
TimeoutException, HttpException, ClientExceptionAuthConfig.onAuthFailure — Centralized callback when token refresh fails
onAuthFailure: (tokenProvider, error) async { ... }RetryConfig.maxDelayMs — Maximum delay cap for exponential backoff
InMemoryCacheStorage.maxEntries — Optional size limit with FIFO eviction
CacheEntry.tryFromJson() — Null-safe factory for corrupted storage data
AuthException now extends UnauthorizedException (was ApiException)
on UnauthorizedException catch alongside normal 401 errorsOnAuthFailureCallback signature: (TokenProvider, Object? error) — includes the failure reason"") are now ignored in onRequest and _performSimplifiedRefreshNothing published for this version
`ApiClientConfig.dataKey` - Configurable key for envelope unwrapping (default: 'data')
ApiClientConfig.dataKey - Configurable key for envelope unwrapping (default: 'data')
*Data methods to extract payload from response.data[dataKey]ApiClientConfig(baseUrl: '...', dataKey: 'result')Data methods (envelope unwrapping) - Extract and format response.data[dataKey] for envelope APIs
getAndDecodeData, getAndDecodeDataOrNull, getAndParseData, getAndParseDataOrNullgetListAndDecodeData, getListAndDecodeDataOrNull, getListAndDecodeDataOrEmpty, getListAndParseData, getListAndParseDataOrNull, getListAndParseDataOrEmptypostAndDecodeData, postAndDecodeDataOrNull, postAndParseData, postAndParseDataOrNullpostListAndDecodeData, postListAndDecodeDataOrNull, postListAndDecodeDataOrEmpty, postListAndParseData, postListAndParseDataOrNull, postListAndParseDataOrEmptyApiClient typed response methods redesigned - 3 clear levels of response handling:
get, post, put, delete, patch → raw Response<T>{verb}AndParse, {verb}AndDecode → format response.data (non-nullable, all verbs){verb}And{Parse|Decode}Data → unwrap envelope then format (GET & POST only, with OrNull/List variants)getAndParseOrNull, getAndDecodeOrNull - Replaced by getAndParseDataOrNull, getAndDecodeDataOrNullpostAndParseOrNull, postAndDecodeOrNull - Replaced by postAndParseDataOrNull, postAndDecodeDataOrNullgetListAndDecode, getListAndParse - Replaced by getListAndDecodeData, getListAndParseDatagetListAndDecodeOrNull, getListAndDecodeOrEmpty - Replaced by getListAndDecodeDataOrNull, getListAndDecodeDataOrEmptygetListAndParseOrNull, getListAndParseOrEmpty - Replaced by getListAndParseDataOrNull, getListAndParseDataOrEmpty`SecureStorageService.withBiometrics()` - Factory constructor for biometric-protected storage
SecureStorageService.withBiometrics() - Factory constructor for biometric-protected storage
userPresence access control flagAndroidOptions.biometric() (API 28+)SentrySetup.addBreadcrumbFromMap() - Helper method for ErrorTrackingConfig.onBreadcrumb
errorTrackingConfig: ErrorTrackingConfig(onError: SentrySetup.captureException, onBreadcrumb: SentrySetup.addBreadcrumbFromMap)Result functional methods - Enhanced Result type with Either-like operations
getOrElse(defaultValue) - Returns value or default on failureflatMap(transform) / flatMapAsync - Chains Result-returning operationsmapError(transform) - Transforms the error typerecover(fallback) - Recovers from failure with fallback valueApiClient flexible parsing methods - Support for any response type, not just JSON
getAndParse(path, parser) - Parse any response type (int, String, DateTime, etc.)putAndParse, patchAndParse - PUT/PATCH variantsSecureStorageService - Auto-clear storage on bad padding exception
read(), readAll(), containsKey()null, {}, false) instead of throwing`ErrorMapperInterceptor` - Automatically transforms DioException into typed ApiException subclasses
ErrorMapperInterceptor - Automatically transforms DioException into typed ApiException subclasses
TimeoutExceptionConnectionExceptionUnauthorizedExceptionForbiddenExceptionNotFoundExceptionHttpExceptionmessage, error, detail, error_description)ApiClientFactorydio: >=5.4.0 <7.0.0 (was >=5.0.0)sentry_flutter: >=9.0.0 <10.0.0 (was >=8.0.0)flutter_secure_storage: >=10.0.0 <11.0.0 (was >=9.0.0)SecureStorageService - Uses new secure defaults (RSA OAEP + AES-GCM) on AndroidSentrySetup - Updated for sentry_flutter 9.x API compatibility`authConfig` parameter in ApiClientFactory.create - Configure authentication directly
authConfig parameter in ApiClientFactory.create - Configure authentication directlyretryConfig parameter in ApiClientFactory.create - Configure retry logic directlycacheConfig parameter in ApiClientFactory.create - Configure caching directlyloggerConfig parameter in ApiClientFactory.create - Configure logging directlyerrorTrackingConfig parameter in ApiClientFactory.create - Configure error tracking directly (Sentry, Crashlytics, etc.)metricsConfig parameter in ApiClientFactory.create - Configure request metrics directlycaptureException → onError, addBreadcrumb → onBreadcrumbApiX is now production-ready with a complete feature set for Flutter/Dart API clients.
ApiX is now production-ready with a complete feature set for Flutter/Dart API clients.
Your coding agent can read these notes before it upgrades. Set up the MCP server →