bifrosted 0.10.2
bifrosted: ^0.10.2 copied to clipboard
The rainbow bridge connecting your app to APIs. A lightweight REST API client and repository pattern with caching, offline support, and error handling.
Changelog #
0.10.2 #
-
Fixed:
RestAPI.hostrejected any value containing a colon._buildUripassedhoststraight intoUri(scheme: 'https', host: host), andUri'shostparameter reads a colon as an IPv6 literal — so a scheme-qualified URL, a port, and therefore any local dev server threwFormatException: Illegal IPv6 address, invalid character (at character 1), an error naming none of the actual causes.hostnow accepts what you'd reasonably put in a config value:hostRequest URL api.example.comhttps://api.example.com/...https://api.example.comas given http://localhost:8080as given — scheme and port preserved 10.0.2.2:3000https://10.0.2.2:3000/...https://example.com/v1base path preserved, not dropped Backward compatible: a bare hostname still resolves to
https. -
An empty
hostnow throwsArgumentErrornaming the likely cause — an app launched without--dart-define-from-file— instead of silently buildinghttps:///pathand failing at the transport layer.
0.10.1 #
- Widened the
loggerre-export.LoggerandLevelalone were not enough to reconfigurebifrostLogger: its defaultDevelopmentFilterdrops every log in release builds and ignoreslevel, so keeping warnings in release meant namingProductionFilter— which required adding a directloggerdependency just to reassign our own global. Now also exportsProductionFilter,DevelopmentFilter,LogFilter,LogPrinter,LogOutput,PrettyPrinter, andSimplePrinter.bifrostLogger = Logger( level: kReleaseMode ? Level.warning : Level.debug, filter: ProductionFilter(), // required; DevelopmentFilter ignores `level` );
0.10.0 #
- Fixed: nothing persisted across app launches.
SharedPrefService.init()calledSharedPreferences.setMockInitialValues({}), which replaces the platform store with an empty in-memory map (SharedPreferencesStorePlatform.instance = InMemorySharedPreferencesStore.withData(...)). Becauseinit()runs on every launch, no preference, token, onboarding flag, or cached response survived a cold start.init()now only reads. If your app appeared to "forget everything," this was why. - Breaking: removed
SharedPrefService.updateInitialMock. It was test scaffolding on a production class, and it set the mock store as a side effect. Replace it withuseMockStorage. - Added
useMockStorage({values})topackage:bifrosted/testing.dart— the storage counterpart touseMockClient. Tests need it for two reasons: there is no SharedPreferences platform plugin in a test process, sogetInstance()otherwise throwsMissingPluginException; and the mock store is a static platform instance, so a value written by one test is still readable in the next unless it is reset. Call it insetUp(or once per file, beforeinitServices):useMockStorage(); // clean slate useMockStorage(values: {'onboarding_done': true}); // seeded state - Added [OnboardingController], a reusable multi-step flow controller. Subclass it, implement
buildResult, and inheritnext/back/skip, answer collection, completion, and the analytics funnel. - It is a plain
ChangeNotifier— no state-management dependency — matching the documented exception for one-time flows that run before the app's main state exists. - Emits the events a funnel is computed from:
started,step_viewed,answered,skipped,back,completed(with duration, reach and skip count), andabandonedon dispose-before-completion, naming the step the user quit on. The event prefix is configurable. step_viewedfires once per step per session, on first arrival only. Re-counting a revisit afterback()would inflate early steps and overstate the funnel's health.- The controller reports events, not rates: a completion rate is a population statistic that one session cannot know.
completed ÷ startedis a query in your analytics tool. back()never clears answers — losing input on a back tap is the most reliable way to cause abandonment.
0.9.0 #
- Breaking:
fetchandmutatereturnBifrostResult<T>instead ofT?. A barenulltold callers that something failed but never what, so every screen could only show generic copy.BifrostFailurecarries aFailureReason, an optional status code, and diagnostic detail. Migrate call sites from a null check to an exhaustive switch:// Before final user = await repo.getUser(id); if (user != null) { print(user.name); } else { // Error was already handled by SystemNotifier } // After switch (await repo.getUser(id)) { case BifrostSuccess(:final data): print(data.name); case BifrostFailure(:final reason): // Error was already handled by SystemNotifier; render an error state. } - Breaking:
mutatetakes a requiredmodel:instead of an optionalfromJson:. This is not a rename — every existing body-lessmutate(apiRequest: ..., invalidateKeys: [...])call (the patternfromJsonbeing optional made possible) is now a compile error. Switch those call sites tosend(), which already returnsbooland takes the sameinvalidateKeys:. - Breaking (behavior): a 2xx response with an empty body through
mutatenow returnsBifrostFailure(FailureReason.parse)instead ofnull—mutatehas nothing to deserialize intomodel:when there's no body. Cache invalidation still runs. Usesend()for writes that don't return a body. - Added
silent:tofetch,mutate, andsend— a background refresh no longer firesSystemNotifier. - Added
CachePolicy(networkFirst,cacheFirst,networkOnly). Cache fallback now keys on request outcome rather thanConnectionChecker.isConnected: online-but-failing previously returned nothing while holding usable cached data. 4xx is excluded from fallback, since a definitive server answer must not be masked by stale data. cacheDurationnow means something undercacheFirst; previously it only bounded the offline fallback.- Added
RestAPI.resolveHeaders()andRestAPI.onUnauthorized()for one-shot 401 refresh, with a shared in-flight future so concurrent 401s cause one refresh, not five. Defaults preserve prior behaviour. - Removed
DeserializationException, which was defined, exported, and never thrown.
0.8.0 #
- Added app-facing service interfaces: [AnalyticsService], [EntitlementService], [PaywallPresenter], and [AttributionService], each with a
NoOpdefault. bifrosted does not call these — they live here so one package covers every project's service shapes. No vendor SDK is added; the app supplies implementations. - [EntitlementService] exposes entitlements as a
Stream, not a one-shot check, because they change mid-session on purchase, restore, and expiry. - [PaywallPresenter] is separate from [EntitlementService] so Superwall, RevenueCat, and custom Flutter paywalls swap without touching call sites.
- [AttributionService] models the platform asymmetry deliberately: Android's Play Install Referrer yields a per-install code, iOS has no runtime equivalent and
captureReferral()returningnullthere is the expected result. - The entitlement and paywall no-ops warn through
bifrostLoggerwhen they swallow a user-initiated action; the analytics and attribution no-ops stay silent, since shipping without analytics and returning null on iOS are both legitimate steady states. bifrostTestEnvgainsanalytics,entitlements,paywalls, andattributiondoubles, all resolvable throughbifrostServiceLocator.
0.7.0 #
- Breaking: Removed the
firebase_performancedependency. bifrosted now defines [PerformanceTracker] / [HttpTrace] / [BifrostHttpMethod] and ships [NoOpPerformanceTracker]; the app supplies the SDK-specific implementation and assignsbifrostPerformanceTracker. - Breaking: Removed
bifrostPerformanceEnabled. Assignconst NoOpPerformanceTracker()to disable metrics. - Breaking:
useMockClientmoved frompackage:bifrosted/bifrosted.darttopackage:bifrosted/testing.dart.package:http/testing.dartis no longer imported by core. - Breaking: Fake data is now seeded by default (
kDefaultFakeSeed), makingModel.fake()reproducible across runs so golden tests are stable. SetbifrostFakeSeed = nullfor the previous random behaviour. - Traces complete without
awaiton the request path, so a slow monitoring SDK no longer adds latency to every request. - Unlike the 0.5.5 tracker, resolution is a plain global with a no-op default rather than
bifrostServiceLocator, so no registration is required andtryBifrostServiceLocatoris unnecessary.
0.6.0 #
- Added direct Firebase Performance HTTP metric instrumentation in [RestAPI]
- Adds
firebase_performanceas a dependency - Toggle via top-level
bifrostPerformanceEnabled(defaults totrue; auto-disabled when Firebase isn't initialized) - Test env disables performance monitoring automatically via
bifrostTestEnv.reset()
0.5.6 #
- Breaking: Removed [PerformanceTracker], [tryBifrostServiceLocator], and HTTP metric hooks from [RestAPI]
- [RestAPI] reuses one [http.Client] per instance; use [RestAPI.closeClient] when discarding an API
0.5.5 #
- Added pluggable [PerformanceTracker] via [bifrostServiceLocator] for HTTP metrics (removed in 0.5.6)
- [RestAPI] uses performance hooks when a tracker is set (no-op when null)
- [MockPerformanceTracker] in
package:bifrosted/testing.dartfor tests (removed in 0.5.6) - App supplies Firebase/Sentry/etc. implementations; bifrosted has no Firebase dependency
0.5.4 #
- Breaking: Simplified [SystemNotifier] to three UI-only callbacks:
onNetworkError(),onUnauthorized(),onRequestFailed({statusCode, body})- Removed
onForbidden,onServerError,onApiError
- Docs: notifiers must handle user-facing UI only; use
bifrostLoggerfor diagnostics
0.5.3 #
- Breaking: Removed
fetchList— usefetch<List<T>>with the samefromJson - Breaking: Removed
endpointonfetch— usecacheKeyonly (it was only used for caching) fetch<R, M>auto-detects top-level JSON array vs object;fromJsonis always the item parser (User.fromJson), return type isUserorList<User>
0.5.2 #
- Removed
unwrapResponse()override - Removed in-memory singleton cache (
_memoryCache,clearMemoryCache,useMemoryCache) - Simplified
fetch/fetchListsignatures
0.5.1 #
- Added
bifrostJsonDecode- Global async JSON decoder- Defaults to synchronous
jsonDecode(works everywhere) - Override to decode on a background isolate for large payloads:
// Dart: bifrostJsonDecode = (body) => Isolate.run(() => jsonDecode(body)); // Flutter: bifrostJsonDecode = (body) => compute(jsonDecode, body); - Web-safe: no
dart:isolatedependency in bifrosted itself
- Defaults to synchronous
0.5.0 #
- Breaking: Removed
dart:iodependency - now works on web/Jaspr - Breaking:
post/put/patch/deletebody is nowObject?instead ofString?- Maps and Lists are auto-encoded to JSON
- Strings are sent as-is
- Breaking: Removed
Deserializerclass - deserialization is now inlined - Added
unwrapResponse()override for wrapped API responses ({"data": {...}}) - Added
mutate<T>()for write operations (POST/PUT/PATCH/DELETE) with optional deserialization - Added
send()for fire-and-forget writes that returnbool - Both
mutateandsendacceptinvalidateKeysfor automatic cache invalidation - Added in-memory singleton cache for deserialized objects
BifrostRepository.clearMemoryCache()to resetuseMemoryCacheparameter onfetch/fetchListto opt out per call
clearAllCache()now only removes bifrost-prefixed keys (no longer wipes all storage)DeserializationExceptionis still exported for custom use
0.4.5 #
- update dependencies
0.4.0 #
- Breaking: Removed generics from
BifrostRepository - Added
bifrostServiceLocator- Set once, used everywhere// At app startup: bifrostServiceLocator = <T>() => Get.find<T>(); - Repositories now have zero boilerplate:
class UserRepo extends BifrostRepository { Future<User?> getUser(String id) => fetch<User>(...); }
0.3.0 #
- Added global mock client support for testing
useMockClient()- Enable mock responses for all RestAPI instancesuseRealClient()- Reset to real HTTP clientssetClientFactory()- Set custom client factory- No more per-API client overrides needed
0.2.2 #
- Fixed build.yaml to correctly combine generated code into
.g.dartfiles- Changed
build_to: cacheandbuild_extensions: .fake.g.part - Generator output now properly merges with json_serializable/freezed
- Changed
0.2.1 #
- Added
build.yamlfor auto-discovery by build_runner- No manual configuration needed - just add the dependency and run build_runner
- Works like freezed/json_serializable out of the box
0.2.0 #
- Added
@generateFakeannotation for code generation - Added
FakeUtilsutility class (usesfakerpackage)fakeForKey(String key)- generates fake data based on field namecreate<T>()- generates fake model from factoryfakeJson()/fakeJsonList()- generic JSON generators
- Added
FakeGeneratorfor build_runner integration- Generates
.fake()extension methods for annotated classes - Works with freezed models
- Generates
0.1.1 #
- Updated README
0.1.0 #
- Initial release
RestAPIabstract class for REST API clients- GET, POST, PUT, PATCH, DELETE methods
- Automatic error handling and logging
- Header management with extra headers support
BifrostRepositoryfor repository pattern with cachingfetch<T>()andfetchList<T>()for automatic deserialization- Offline-first with cache fallback
- Automatic cache expiration
SystemNotifierinterface for global error handlingonNetworkError(),onUnauthorized(),onForbidden()onServerError(),onApiError()
StorageServiceinterface for pluggable storage backendsConnectionCheckerinterface for connectivity detection- Uses
loggerpackage for logging - Comprehensive test suite