data_repository 1.0.2
data_repository: ^1.0.2 copied to clipboard
A repository layer for Flutter that unifies typed HTTP requests, interceptors, response caching, pagination and error normalisation behind one API.
1.0.2 #
Fixes a network failure being reported as the generic default message. No API changes.
-
RemoteRepository.handleErrornow falls back toApiResponse.causewhen the response carries no error. AnonErrorinterceptor that decodes the body unconditionally — the common hand-rolled JSON interceptor — setserrorto the decode of a body that does not exist on a transport failure, clearing theApiErrorthe transport produced.handleErrorthen formattednulland reported the default "Something went wrong" with code7011, so a device with no connection got no useful message. The original throwable is still oncause, so it is normalised instead: a failed host lookup reports "Please check your internet connection and try again" withErrorCodes.network.An interceptor that sets its own error still wins; this only applies when the error is absent.
1.0.1 #
Fixes a runtime cast failure in copyWith. No API changes.
ApiRequest.copyWith(body: ...)no longer throwstype '_Map<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>?'. The sentinel that distinguishes an omitted argument from an explicit null typesbodyasObject?, so the analyzer stopped rejecting aMap<dynamic, dynamic>— aMap.fromcopy, a platform-channel map, a map literal with no context type — and the cast failed at runtime instead. Such a map is now re-keyed; a non-map argument raises anArgumentErrornamingbodyrather than a bare cast error.ApiResponse.copyWith(body: ...)had the same latent failure, reachable from any interceptor doingresponse.copyWith(body: decoded). A dynamic map is re-keyed whenBodyTypeexpects string keys; a genuine type mismatch is still reported.
1.0.0 #
A breaking release. Every rename and signature change is listed below with its migration; most apps need only the mechanical renames in step 1.
Migrating from 0.5.x #
1. Renames — mechanical, no behaviour change:
| 0.5.x | 1.0.0 |
|---|---|
ExceptionFormater |
ExceptionFormatter |
utils/exception_formater.dart |
utils/exception_formatter.dart |
Jsonutils |
JsonUtils |
ApiRequest.ovveride500Error |
ApiRequest.override500Error |
CacheManager.savToCache |
CacheManager.saveToCache |
# from the project root. On Linux use `sed -i` instead of `sed -i ''`.
grep -rl 'ExceptionFormater\|Jsonutils\|ovveride500Error\|savToCache' lib test \
| while IFS= read -r f; do
sed -i '' \
-e 's/ExceptionFormater/ExceptionFormatter/g' \
-e 's/Jsonutils/JsonUtils/g' \
-e 's/ovveride500Error/override500Error/g' \
-e 's/savToCache/saveToCache/g' \
-e 's/exception_formater/exception_formatter/g' "$f"
done
2. build and resolve are now Futures. Only affects code calling them
directly; a custom ApiProvider is the usual case:
// before
request = request.build;
return ApiResponse(...).resolve;
// after
request = await request.build;
return await ApiResponse(...).resolve;
3. ApiProvider.send takes an optional RequestOptions. Custom providers
and hand-written fakes must widen their signature:
// before
Future<ApiResponse<R, I>> send<R, I>(ApiRequest<R, I> request) { ... }
// after
Future<ApiResponse<R, I>> send<R, I>(
ApiRequest<R, I> request, [
RequestOptions options = const RequestOptions(),
]) { ... }
Interceptors need no change: the hooks now return FutureOr, which a
synchronous implementation already satisfies.
4. Transport failures report 0, not 420. If you branched on the old
magic number, use the named constant — and note a thrown
ApiError('...', 401) now surfaces its own status instead of being
overwritten:
// before
if (response.statusCode == 420) showOffline();
// after
if (response.statusCode == ApiResponse.transportFailure) showOffline();
5. isSuccessful now also requires error == null. A 2xx response whose
interceptor chain threw is no longer reported as successful. If you relied on
the status alone, check response.statusCode directly.
6. Unresolved dataKey paths return null instead of the whole body. This
is the change most likely to look like a regression: a key that never existed
used to silently fall back to the root and appear to work. If a body starts
coming back null after upgrading, set ApiConfig().logger and look for
did not resolve — it names the failing segment.
7. ErrorDescription.key defaults to the empty path (the body itself)
rather than 'error'. That matches what the old root-fallback produced for
most APIs. If your errors really are nested, say so explicitly:
ErrorDescription(key: 'error').
8. Smaller signature changes. handleRequest lost its unused retry
parameter, ApiRequest.copyWith lost its unused pagination parameter, the
Extra class and ApiRequest.extra were removed (accepted but never used),
CacheManager.getCachedData is typed Future<String?>, and the dynamic.asInt
extension was dropped in favour of the Object one — a nullable receiver now
needs ?.asInt.
9. SDK floor. Dart ^3.10.3 / Flutter >=3.38.4, required by the upgraded
path_provider chain.
Breaking #
- Renamed misspelled public API, with no aliases kept — see migration step 1.
ApiRequest.buildandApiResponse.resolveare nowFutures, following the async interceptor hooks.ApiProvider.sendtakes an optionalRequestOptions, and gainedclose().ApiInterceptorhooks returnFutureOrrather than a plain value. Existing synchronous interceptors satisfy this unchanged.- Failed transport reports
ApiResponse.transportFailure(0) instead of the invented420, and a cancelled call reportsApiResponse.cancelled(-1). ApiResponse.isSuccessfuladditionally requireserror == null.- A
dataKey/paginationKey/ error key that does not resolve now yields null and logs the failing segment, instead of silently returning the whole body. ErrorDescription.keydefaults to the empty path rather than'error'.JsonUtils.convertToJsonthrowsJsonSerializationExceptioninstead of silently producing the string"null".- Removed
MyHttpOverrides, which disabled TLS certificate validation for the whole process. - Removed the
Extraclass andApiRequest.extra; removed the unusedretryparameter fromhandleRequestandpaginationfromApiRequest.copyWith. - Removed the
dynamic.asIntextension in favour of theObjectone. - Removed dead, fully commented-out files:
pagination_handler.dart,base_api_service.dart,dio_api_provider.dart,chopper_api_provider.dart, and the no-opString.normalizeUrlextension. CacheManager.getCachedDatais typedFuture<String?>;ApiResponse.extrais nowfinal.mockitomoved fromdependenciestodev_dependencies; it is no longer pulled into consumer apps.
Deprecated #
-
ApiRequest.nestedKey. It existed only becausedataKeycould resolve a single key, so an envelope needed a second anchor. With both now dotted paths:// before nestedKey: 'result', dataKey: 'data' // after dataKey: 'result.data', paginationKey: 'result'It continues to work unchanged — including scoping
dataKeyrelative to it — and will be removed in a future release.
Added #
- Async interceptors. Every
ApiInterceptorhook returnsFutureOr, so an interceptor can await — refresh a token, read from secure storage — before the request goes out. RetryPolicywith exponential backoff and jitter, configurable app-wide onRemoteRepository, per request viaApiRequest.retryPolicy, or per call viaRequestOptions.retry. Retries transport failures, timeouts, 408, 429 and 5xx, and only for idempotent methods. The request is rebuilt on each attempt, so a token refreshed inonErroris picked up by the replay.CancellationTokenandRequestOptions.cancelToken. A cancelled call returnsApiResponse.cancelledwithisCancelled == truerather than an error the UI must filter out.FileLocalRepository, a persistentLocalRepositorybuilt onCacheManager, so caching works without writing a storage adapter first.- In-flight de-duplication. Identical concurrent GETs share one network
call. On by default; disable per call with
RequestOptions.skipDeduplicationor globally onRemoteRepository. LoggingInterceptor, emitting throughApiConfig().logger, redactingAuthorization,CookieandX-Api-Key, and truncating long bodies.- Upload and download progress via
RequestOptions.onSendProgressandonReceiveProgress. RequestOptions, carrying per-call cancellation, retry, progress and timeout so none of these becomes another parameter onhandleRequest.- Dotted paths for
dataKey,paginationKeyandErrorDescription.key:'response.payload.items', list indices ('data.pages[0].items') and backslash-escaped literal dots. A single key with no dots behaves exactly as before.JsonPathis exported for direct use. paginationKey, a dotted path to the object carrying the pagination fields, defaulting to the root.JsonInterceptoris now part of the package, instead of something each consumer copy-pastes out of the example.ApiConfig().logger— an opt-in sink for diagnostics. The package no longer prints to the console of an app that did not ask for it.HttpApiProvider({http.Client? client})for connection reuse and for testing withpackage:http/testing.dart.ApiResponse.causeandstackTrace;ApiErrorvalue equality; andApiErrorStatus.hasHttpStatusCodeas an extension.
Fixed #
- Errors are no longer flattened on the exception path. A custom exception
thrown by an interceptor is recoverable via
ApiResponse.cause, and a thrownApiError('...', 401)surfaces asstatusCode: 401instead of being overwritten by a hardcoded420. - A throwing interceptor is no longer swallowed by
ApiResponse.resolve; the failure is reported on the response instead of returning a half-resolved one. - Multipart uploads could drop file parts: parts were added from an
asynccallback passed toMap.forEach, which discards futures, so a part read from disk could resolve after the request was sent. - An unknown file extension no longer throws
FormatException; parts fall back toapplication/octet-stream. - URLs no longer gain a trailing
?when a request has no query parameters. - An explicit
queryentry now takes precedence over one embedded inpath(previously the reverse). copyWithcan clear a field:copyWith(body: null)was a silent no-op on bothApiResponseandApiRequest.- The empty-collection cache guard now works — it compared a type argument against a type and was always false, so empty lists were cached.
- Error classification uses
ischecks instead of matchingruntimeType.toString(), which stopped matching under release obfuscation and never matched subclasses. CacheManagerno longer derives filenames from the last path segment, which collided distinct keys such asposts/1andcomments/1onto one file.MapRepository.getTimeno longer throws on a missing key.JsonInterceptor.onErrordecodeddart:convert's top-leveljsonobject instead of the response body in its fallback path.
Changed #
HttpApiProviderissues requests throughClient.send, which is what enables streaming progress and cancellation.
Packaging #
- Rewrote
pubspec.yamlmetadata: a fullerdescription(147 chars, within pub.dev's 60-180 range), plusissue_tracker,documentationandtopics.homepagepoints at the live demo andrepositoryat the source, instead of both duplicating the same URL. - Added
.pubignore, shrinking the published archive from 296 KB to 36 KB by excluding the example's generated native scaffolding and build artefacts. - Fixed the dead demo link in the README (
data-repository.wiseminds.ccno longer resolves); it now points at the GitHub Pages deployment CI publishes. - Test suite grown from 1 test to 84; CI now runs analysis, formatting and tests.
Dependencies #
- All direct and dev dependencies upgraded to latest:
http^1.6.0,mime^2.1.0,path_provider^2.1.6,http_parser^4.1.2,mockito^5.8.1,flutter_lints^6.0.0. - SDK floor raised to Dart
^3.10.3/ Flutter>=3.38.4, required by the upgradedpath_providerchain (path_provider_foundation2.6.0); the other dependencies alone would allow Dart 3.4. - Dropped the unused
testdev dependency — the suite usesflutter_test.
0.5.1 #
- added client exception support
0.5.0 #
- filter out network error correctly
- Updated packages
- Updated constraints
- Added sample test to readme
0.4.5 #
- Updated error messaage
0.4.4 #
- Fixed body payload for patch and delete methods in the default http provider
0.4.3 #
- Fixed double slash in path segment
0.4.0 #
- Removed chunkCount
- Added getter to check has next page and previous page
0.3.0 #
- Upgraded packages
0.2.4 #
- Added support for patch on http client
0.2.3 #
- Added mimetype for file upload
0.2.2 #
- Updated exception filter
0.2.1 #
- Fixed analysis issues
0.2.0 #
- Added id request to identify unique request
- Updated README
- Added example app
0.1.10 #
- updated suport for bytes upload
0.1.9 #
- Added suport for bytes upload
0.1.8 #
- Removed api provider from base api service
0.1.7 #
- Fixed cache algorithm
0.1.6 #
- Fixed query parameters not added to Uri
0.1.5 #
- Fixed query parameters not added to Uri
0.1.4 #
- Fixed cache not resolving data
0.1.3 #
- Fixed url encoding issue
- Fixed multi-part request builder
0.1.2 #
- updated data repository, added api provider to remote repository
0.1.1 #
- updated data repository
0.1.0 #
- added request to data repository parameters
0.0.8 #
- Updated Api URI parser
0.0.7 #
- optimized api provider
0.0.6 #
- optimized api provider
0.0.5 #
- updated pagination
0.0.4 #
- Added pagination to request
0.0.3 #
- Added header interceptor
0.0.2 #
- Refactored project
0.0.1 #
- Finished basic setup