apix 2.1.0
apix: ^2.1.0 copied to clipboard
Production-ready Flutter/Dart API client with auth refresh queue, exponential retry, smart caching and error tracking (Sentry-ready). Powered by Dio.
2.1.0 #
Fixed #
-
*AndDecode/*AndParse— Parsing failures now surface as typedApiException(critical)- New
ParsingException(extendsApiException) thrown whenfromJsonor a customparsercallback throws (e.g. truncated JSON, type mismatch) - Closes a gap in the 2.0.0 contract:
on ApiException catchnow catches every client-side parse failure originalErrorandstackTraceare preserved- User-thrown
ApiExceptionfrom insidefromJson/parseris rethrown unchanged (no double-wrap)
- New
-
AuthInterceptor— Network blip no longer logs the user out (major)- When the refresh request fails with a connection / timeout error, the original request is rejected with
NetworkException(typed:ConnectionException,TimeoutException) onAuthFailureis not invoked on network failures- Real auth failures (401/403 from the refresh endpoint) still produce
AuthExceptionand callonAuthFailure(regression preserved)
- When the refresh request fails with a connection / timeout error, the original request is rejected with
-
AuthInterceptor— Token provider failures now typed (moderate)- New
TokenProviderException(operation: read | write | clear)(extendsApiException) - Wraps errors thrown by
getAccessToken,getRefreshToken, and the user-suppliedonTokenRefreshedcallback - Surfaces directly to callers:
on TokenProviderException catchdistinguishes credential storage issues from network/HTTP errors
- New
-
AuthExceptionnow preserves the underlying causeAuthException(message, originalError: ..., stackTrace: ...)— typed cause flows through to the caller viaoriginalError
Added #
-
RetryConfig.respectRetryAfter— Honor the server'sRetry-Afterheader on retryable responses (defaulttrue)- Parses both delta-seconds (
"60") and HTTP-date ("Wed, 21 Oct 2026 07:28:00 GMT") formats per RFC 7231 §7.1.3 - Capped at
RetryConfig.maxDelayMs; falls back to exponential backoff if the header is absent or malformed - Public
RetryInterceptor.parseRetryAfter(value, {now})exposed for advanced use and testing
- Parses both delta-seconds (
-
ApiClientConfig.strictContentType— Detect captive portals / wrong Content-Type (defaultfalse)- When
true,*AndDecodemethods verify the response'sContent-Typestarts withapplication/json - Throws
UnexpectedContentTypeException(extendsApiException) on mismatch — exposesexpectedContentTypeandactualContentTypefields *AndParsemethods are unaffected (they accept any payload type by design)
- When
-
ApiClientConfig.responseValidator— Hook for legacy APIs that signal business errors via HTTP 200ResponseValidatortypedef:ApiException? Function(Response)- Returning a non-null exception fails the request with that typed exception; returning
nulllets the response pass through - Only fires on 2xx responses (4xx/5xx still go through
ErrorMapperInterceptor) - Preserves the exact subclass returned (e.g. a custom
BusinessException)
Changed #
*AndDecodemethods now use<dynamic>internally with explicit_requireDatavalidation (instead of relying on Dio's eager generic cast)- Eliminates confusing
TypeErroron non-JSON responses; replaced by clearApiExceptionmessages _requireDatanow also throwsApiExceptionwhen the body is non-null but not aMap<String, dynamic>
- Eliminates confusing
2.0.0 #
Breaking #
ApiClientmethods now throwApiExceptioninstead ofDioException- All HTTP methods (
get,post,put,delete,patch) and typed variants unwrapDioExceptionautomatically - Code using
on DioException catchonApiClientmethods must migrate toon ApiException catch(or subtypes) client.dio(raw Dio access) still throwsDioException— onlyApiClientmethods are affectedgetResult()handles bothApiExceptionandDioException(fallback for raw Dio usage)
- All HTTP methods (
Fixed #
-
AuthInterceptor— Refresh request isolation (critical)- Refresh requests no longer inject the expired access token in the Authorization header
- Refresh requests that return 401 no longer cause a deadlock (recursive refresh loop)
- Auth-retried requests that fail again with 401 no longer trigger infinite refresh-retry loops
-
ApiClientmethods now throw typedApiExceptiondirectly (critical)- All HTTP methods (
get,post,put,delete,patch) unwrapDioExceptionautomatically on ClientException catch,on UnauthorizedException catch, etc. now work as expected- No need to catch
DioExceptionand extract.errormanually getResult()also works correctly with bothApiExceptionandDioException(fallback for raw Dio usage)
- All HTTP methods (
-
CacheInterceptor— Cache key generation (critical)- Fixed double-encoding of query parameters in cache keys
invalidateUrl()now resolves relative URLs against the client's base URL
-
CacheInterceptor— Deduplicated requests (major)- Deduplicated requests now use the main Dio instance (with auth, logging, etc.) instead of a bare Dio that lost all interceptors
-
ApiClient— Null safety on response body (major)*AndDecodemethods now throwApiExceptioninstead ofTypeErroron null response body (e.g. 204 No Content)_extractDatanow throwsApiExceptioninstead ofTypeErroron non-Map responses
-
ErrorMapperInterceptor— Nested error message extraction (major)- Now extracts messages from nested error objects:
{ "error": { "message": "..." } } - Supports common API formats:
error.message,error.detail,error.description captureStatusCodesfilter now applies consistently inonError(previously only filtered inonResponse)
- Now extracts messages from nested error objects:
-
MetricsInterceptor— Request ID collisions (moderate)- Uses monotonic counter instead of
_inFlight.lengthfor unique IDs - Adds orphan cleanup for entries older than 5 minutes
- Uses monotonic counter instead of
-
SecureStorageService— Corruption blast radius (moderate)read()andcontainsKey()now delete only the corrupted key instead of callingdeleteAll()
-
Interceptor resilience (moderate)
AuthInterceptor,RetryInterceptor,CacheInterceptornow wrap asynconRequest/onError/onResponsein try/catch to prevent silent request hangs on unexpected exceptions
-
Sentry noise filter (minor)
- No longer accidentally filters out ApiX's own
TimeoutException,HttpException,ClientException
- No longer accidentally filters out ApiX's own
Added #
-
AuthConfig.onAuthFailure— Centralized callback when token refresh fails- Called exactly once per refresh attempt (even with concurrent requests queued)
- Receives the error object for diagnostic:
onAuthFailure: (tokenProvider, error) async { ... } - Use to clear tokens, redirect to login, or log the failure reason
-
RetryConfig.maxDelayMs— Maximum delay cap for exponential backoff- Defaults to 30000ms (30 seconds)
- Prevents overflow on high attempt counts
-
InMemoryCacheStorage.maxEntries— Optional size limit with FIFO eviction -
CacheEntry.tryFromJson()— Null-safe factory for corrupted storage data
Changed #
AuthExceptionnow extendsUnauthorizedException(wasApiException)- Catchable with
on UnauthorizedException catchalongside normal 401 errors
- Catchable with
OnAuthFailureCallbacksignature:(TokenProvider, Object? error)— includes the failure reason- Empty tokens (
"") are now ignored inonRequestand_performSimplifiedRefresh
1.4.0 #
Added #
-
ApiClientConfig.dataKey- Configurable key for envelope unwrapping (default:'data')- Used by all
*Datamethods to extract payload fromresponse.data[dataKey] - Customizable per client:
ApiClientConfig(baseUrl: '...', dataKey: 'result')
- Used by all
-
Data methods (envelope unwrapping) - Extract and format
response.data[dataKey]for envelope APIs- GET single:
getAndDecodeData,getAndDecodeDataOrNull,getAndParseData,getAndParseDataOrNull - GET list:
getListAndDecodeData,getListAndDecodeDataOrNull,getListAndDecodeDataOrEmpty,getListAndParseData,getListAndParseDataOrNull,getListAndParseDataOrEmpty - POST single:
postAndDecodeData,postAndDecodeDataOrNull,postAndParseData,postAndParseDataOrNull - POST list:
postListAndDecodeData,postListAndDecodeDataOrNull,postListAndDecodeDataOrEmpty,postListAndParseData,postListAndParseDataOrNull,postListAndParseDataOrEmpty
- GET single:
Changed #
ApiClienttyped response methods redesigned - 3 clear levels of response handling:- Standard:
get,post,put,delete,patch→ rawResponse<T> - Parse/Decode:
{verb}AndParse,{verb}AndDecode→ formatresponse.data(non-nullable, all verbs) - Data:
{verb}And{Parse|Decode}Data→ unwrap envelope then format (GET & POST only, with OrNull/List variants)
- Standard:
Removed #
getAndParseOrNull,getAndDecodeOrNull- Replaced bygetAndParseDataOrNull,getAndDecodeDataOrNullpostAndParseOrNull,postAndDecodeOrNull- Replaced bypostAndParseDataOrNull,postAndDecodeDataOrNullgetListAndDecode,getListAndParse- Replaced bygetListAndDecodeData,getListAndParseDatagetListAndDecodeOrNull,getListAndDecodeOrEmpty- Replaced bygetListAndDecodeDataOrNull,getListAndDecodeDataOrEmptygetListAndParseOrNull,getListAndParseOrEmpty- Replaced bygetListAndParseDataOrNull,getListAndParseDataOrEmpty
1.3.0 #
Added #
-
SecureStorageService.withBiometrics()- Factory constructor for biometric-protected storage- iOS: Face ID / Touch ID via
userPresenceaccess control flag - Android: Biometric-backed encryption via
AndroidOptions.biometric()(API 28+) - Customizable prompt titles for Android
- iOS: Face ID / Touch ID via
-
SentrySetup.addBreadcrumbFromMap()- Helper method forErrorTrackingConfig.onBreadcrumb- Simplifies error tracking configuration to a single line
- Example:
errorTrackingConfig: ErrorTrackingConfig(onError: SentrySetup.captureException, onBreadcrumb: SentrySetup.addBreadcrumbFromMap)
-
Resultfunctional methods - Enhanced Result type with Either-like operationsgetOrElse(defaultValue)- Returns value or default on failureflatMap(transform)/flatMapAsync- Chains Result-returning operationsmapError(transform)- Transforms the error typerecover(fallback)- Recovers from failure with fallback value
-
ApiClientflexible parsing methods - Support for any response type, not just JSONgetAndParse(path, parser)- Parse any response type (int, String, DateTime, etc.)putAndParse,patchAndParse- PUT/PATCH variants- Note: OrNull and List variants were redesigned in 1.4.0 as Data methods with envelope unwrapping
Fixed #
SecureStorageService- Auto-clear storage on bad padding exception- Handles corrupted encrypted data (e.g., after app reinstall or key rotation)
- Affected methods:
read(),readAll(),containsKey() - Returns safe defaults (
null,{},false) instead of throwing
1.2.0 #
Added #
ErrorMapperInterceptor- Automatically transformsDioExceptioninto typedApiExceptionsubclasses- Timeout errors →
TimeoutException - Connection errors →
ConnectionException - HTTP 401 →
UnauthorizedException - HTTP 403 →
ForbiddenException - HTTP 404 →
NotFoundException - Other HTTP errors →
HttpException - Message extracted from response body (
message,error,detail,error_description) - Added automatically to all clients created via
ApiClientFactory
- Timeout errors →
Changed #
- Dependencies updated for latest versions compatibility:
dio:>=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
1.1.0 #
Added #
authConfigparameter inApiClientFactory.create- Configure authentication directlyretryConfigparameter inApiClientFactory.create- Configure retry logic directlycacheConfigparameter inApiClientFactory.create- Configure caching directlyloggerConfigparameter inApiClientFactory.create- Configure logging directlyerrorTrackingConfigparameter inApiClientFactory.create- Configure error tracking directly (Sentry, Crashlytics, etc.)metricsConfigparameter inApiClientFactory.create- Configure request metrics directly
Changed #
- Renamed
captureException→onError,addBreadcrumb→onBreadcrumb - Updated README documentation to match actual API signatures
1.0.0 #
🎉 First Stable Release #
ApiX is now production-ready with a complete feature set for Flutter/Dart API clients.
Features #
- Core API Client - Dio-powered client with configurable timeouts, headers, and interceptors
- Authentication - TokenProvider interface with refresh token queue and automatic retry
- Secure Token Storage - Built-in SecureTokenProvider with flutter_secure_storage
- Retry Logic - Exponential backoff with configurable status codes and max attempts
- Smart Caching - CacheFirst, NetworkFirst, and HTTP-aware strategies with TTL
- Observability - Logger, Metrics, and Sentry interceptors for debugging and monitoring
- Result Pattern - Functional error handling with Success/Failure types
- Exception Hierarchy - NetworkException, HttpException, and typed client/server errors
Highlights #
- 401 tests passing
- Full API documentation
- Example app included
- CI/CD with GitHub Actions
0.3.0 #
Added #
-
SecureStorageService: Wrapper for
flutter_secure_storagewith simplified APIwrite(key, value),read(key),delete(key),deleteAll()containsKey(key),readAll()- Default secure options for Android and iOS
- Injectable
FlutterSecureStoragefor custom configuration
-
SecureTokenProvider: Ready-to-use
TokenProviderimplementation- Zero-boilerplate token management
- Configurable storage keys (
accessTokenKey,refreshTokenKey) - Exposed
storagegetter for secondary usage (Firebase tokens, API keys) - Works with
SecureStorageServicevia composition
-
Simplified Token Refresh: New
refreshEndpointapproach inAuthConfigrefreshEndpoint: Relative URL for automatic refresh callsrefreshHeaders: Optional custom headers for refresh requestonTokenRefreshed: Callback with rawResponsefor parsingrefreshTokenBodyKey: Configurable body key (default: 'refresh_token')hasSimplifiedRefresh: Getter to check if simplified flow is configured
Changed #
AuthInterceptornow supports both simplified and legacy refresh flows- Simplified flow takes priority when
refreshEndpointis configured
Backward Compatibility #
- Existing
onRefreshcallback still works as before - All new fields are optional with sensible defaults
0.0.1 #
- Initial release with core features:
- ApiClient with configurable timeouts and interceptors
- TokenProvider interface for authentication
- AuthInterceptor with refresh token queue
- RetryInterceptor with exponential backoff
- CacheInterceptor with multiple strategies
- LoggerInterceptor for debugging
- ErrorTrackingInterceptor for error reporting
- Result pattern for functional error handling