firewatch 1.13.0
firewatch: ^1.13.0 copied to clipboard
Lightweight Firestore repositories for Flutter: single-doc, collection, and collection-group repos that react to auth, stream updates, and support live-window pagination.
Changelog #
1.13.0 #
Added #
WriteAckPolicy— offline-safe write acks. Firestore write futures complete only on the server ack, so while offline an awaited write hangs indefinitely even though it is already durably queued in the local mutation queue. Worse, a hung write bricks its Command for the rest of the session:command_it's single-execution guard silently no-opsrun()while a previous execution is in flight (andrunAsync()returns the original hung future), and since repos are cached in registries, every later invocation of that Command is silently dropped — one offline write becomes session-long data loss. All three repositories now accept awriteAckPolicyconstructor parameter; with anackGraceset (~1–2s recommended), every write Command,*Directwrite, and batch commit resolves optimistically once the grace elapses. The policy is applied inside each Command's function, so the Command completes within the grace and the guard recovers — the bricking failure mode is structurally impossible. Errors arriving before the grace still throw; errors arriving after it can no longer throw (the future has already resolved) and are instead routed fire-and-forget to the repository's existingonErrorhandler — the same handler stream/fetch errors use — so a late rules rejection stays observable instead of vanishing. The default (ackGrace: null) preserves the legacy await-indefinitely behavior exactly. Transactions are not covered (they require connectivity).create(Map<String, dynamic> data)onFirestoreCollectionRepository. Mints the document ID locally via.doc()(no server round-trip), writes under the ack policy, and returns the ID — so with a graced policy it resolves with the real ID within the grace even while offline. Prefer it overadd/addDirectin new code.
Changed #
add/addDirectnow mint the document ID locally (exactly whatCollectionReference.adddoes internally) and run the underlyingsetunder the repo'swriteAckPolicy, so they too resolve with the real ID under a graced policy. With the default policy their behavior is unchanged: the future completes only on server ack.addDirectis now an alias ofcreate.
1.12.0 #
Added #
lastError/hasErroron list repositories. Collection and collection-group repos now exposeValueNotifier<Object?> lastError(and ahasErrorconvenience getter), set when a fetch fails after exhausting the retry budget — on both the one-shot (subscribe: false) and snapshot-listener paths. It is cleared by any successful snapshot and on hard query/auth/dependency swaps. This makes "initialized but the fetch FAILED" (value == [],lastError != null) distinguishable from "initialized and genuinely empty", so UIs stop rendering "add your first item" empty states over load failures (Ratio-et-Ars/hivebloom#1202).isFromCacheon list repositories. Mirrors the latest snapshot'smetadata.isFromCache. With Firestore persistence enabled (notably on web), an offlineget()can resolve from cache — including a cached-empty result — so this lets consumers distinguish "server-confirmed empty" from "cached/unknown empty".
Fixed #
- One-shot fetches now honor
maxRetries/retryDelay. Previously the retry budget was only consulted by the snapshot-listener error path; asubscribe: falserepo gave up on the first.get()failure and — worse — still flippedhasInitializedtotrueover an emptyvalue, making a failed fetch look exactly like a successful empty one. The one-shot path now retries with the same linear backoff and epoch-guard discipline as the listener path (retries are abandoned if the repo is swapped or disposed mid-backoff), and only surfaceslastErroronce the budget is exhausted.
Changed #
showEmptynow also requires!hasError. A failed fetch leaves the list empty too;showEmptyno longer reportstruefor it.
1.11.0 #
Changed #
refresh()is now a soft refresh. It no longer clearsvalueor resetshasInitializedtofalsewhile the re-fetch is in flight. The already-loaded list stays visible (andisRefreshingistrue,isInitializingstaysfalse) so pull-to-refresh UIs keep showing existing items under the spinner instead of flashing a blank list / empty-state on every pull. Cold loads are unaffected — with nothing loaded yet there is nothing to preserve, so the first results simply populatevalue. The internal_swapgained apreserveInitializedflag to support this;setQuery, auth/dependency changes, and the initialstart()still do a hard (clearing) swap.
1.10.3 #
Fixed #
-
loadMore()is no longer silently dropped when called while a previous window resize is still settling (a rapid second tap, or aloadMoreduring an auth/dependency settle). The growth is now coalesced and applied once the in-flight resize completes. Affects both the collection and collection-group repos. -
hasMoreis nowfalsewhenpaginate: false. Previously a non-paginated repo with more documents thanpageSizelefthasMorestucktrue, even though the snapshot already held the full result set andloadMore()did nothing useful. -
FirestoreDocRepository.readyno longer hangs when the repo is disposed before its first load completes.dispose()now completes thereadyfuture (with the current value) so a pendingawait repo.readyresolves instead of awaiting forever. -
notifierFor(key)now returns a stable instance that keeps updating across page-window changes. Previously, when an item left the live window its per-item notifier was discarded and a new instance was created if the item reappeared, so a detail view holding the reference silently stopped updating. The notifier is now seeded from the cache, goesnullwhen the item leaves the window, and updates again when it re-enters. (The notifier map is also bounded by the keys callers actually request — it no longer auto-creates one per document.)
Docs #
- Clarified that
FirestoreDocRepositorywrite Commands resolve the target document path from the current auth UID at write time — don't hold and replay a write across an auth change (a model captured as user A, written after switching to user B, lands at B's path). - Security: documented that collection-group queries must be scoped by
owner/uid (e.g.
.where('ownerId', isEqualTo: uid)). The repo passesuidbut adds no filter itself, so an unfiltered builder reads other tenants' documents (README + class doc). - Batch atomicity: documented that batch writes are atomic per 500-op chunk only — longer lists commit as multiple sequential batches and are not all-or-nothing (corrected the package overview's "atomic" wording).
- Caching: documented the cache-first staleness behavior — after a cache
hit, a failed server read leaves the stale cached value on screen (surfaced
via
onError, not reverted).
1.10.2 #
Changed #
- Internal: the collection and collection-group repos now share a single
lifecycle base (
QueryListRepositoryBase), and all three repos share the auth / epoch / subscription primitives (AuthReactiveLifecycle). This removes the duplicated swap / cache-prime / window-resize / snapshot / dispose logic that had drifted between the collection and collection-group repos — the very drift that required the 1.10.1 fixes — so that class of bug can no longer recur. No public API changes.
Fixed #
- Collection-repo pagination (
loadMore) stream errors now suppress errors from a detached (signed-out) repo, matching the primary listener and the collection-group repo.
1.10.1 #
Fixed #
FirestoreCollectionGroupRepositorysafety parity with the doc and collection repos. Three guards that shipped for the sibling repos (in 1.5.1 and 1.8.1) were never ported to the collection-group repo:dispose()now increments the epoch before tearing down, so an in-flight_swap(e.g. one awaiting a cache read) can no longer write to the disposed notifiers. Previously this could throw "A ValueNotifier was used after being disposed" or silently repopulate a disposed repo.- Sign-out now awaits the listener cancel before returning, so the
native Firestore listener is fully torn down before the auth token is
invalidated (prevents a
PERMISSION_DENIEDerror loop on sign-out). - The stream
onErrorcallbacks now suppress errors when the repo is auth-detached (signed out), matching the collection repo, so a dying listener can't surface a spurious permission error toonError.
1.10.0 #
Added #
refresh()onFirestoreDocRepository. A public method to force a re-read of the document using the current auth state, mirroring the existingFirestoreCollectionRepository.refresh(). For one-shot repos (subscribe: false) this is how you pick up out-of-band changes (another device, a Cloud Function, a webhook): call it on app resume, on pull-to-refresh, or after a local write. The refetch happens in place, keepingvalueandhasInitializeduntil fresh data arrives, so no loading / uninitialized state flashes over the already-loaded document.
Fixed #
- One-shot
FirestoreDocRepositorynow clearsvaluewhen arefresh()finds the document deleted, matching the live-listener path (previously a stale value lingered).
1.9.0 #
Added #
- Partial upsert on
FirestoreDocRepository(#19).- New Command
setFields(Map<String, dynamic>)— likepatch, but usesset(..., SetOptions(merge: true))so the document is created if it doesn't exist. Use this for opt-in flows, default-setting writes, or any partial write where the doc may not have been initialized yet. - Existing
patchkeeps itsupdate()semantics (throwsnot-foundon missing doc) for callers that rely on that.
- New Command
- Direct writes on
FirestoreDocRepository, mirroring the existingFirestoreCollectionRepositoryDirect API:writeDirect(T)—set(merge: true)with full modelupdateDirect(T)— full-model update (throws if missing)patchDirect(Map)— partial update (throws if missing)setFieldsDirect(Map)— partial upsert (create if missing)deleteDirect()— delete- Use these when you need to fire rapid, overlapping writes that would otherwise be rejected by the Command single-execution guard.
1.8.1 #
Fixed #
- Sign-out race with snapshot retry loop (#17): On sign-out, the
subscription cancel was fire-and-forget, so the native Firestore listener
could fire
PERMISSION_DENIEDerrors before the Dart-side cancel reached the native layer. Combined with the retry mechanism from 1.8.0, this created an error loop. The null-UID path in_swap()now awaits the subscription cancel, andonErrorsuppresses retries when the repo is auth-detached.
1.8.0 #
Added #
- Automatic retry on snapshot listener errors. When a Firestore snapshot
listener dies (e.g.
PERMISSION_DENIEDbecause a parent document hasn't been committed server-side yet), the repository now retries with linear backoff instead of leaving the listener permanently dead. - New
FirestoreCollectionRepositoryconstructor parameters:maxRetries(default: 5) — number of retry attempts before giving up.retryDelay(default: 500 ms) — base delay, multiplied by attempt number (500 ms, 1 s, 1.5 s, 2 s, 2.5 s).
- Retry counter resets on successful snapshot or on auth/dependency/query
change. After
maxRetriesexhausted, the repo settles intohasInitialized = true/isLoading = false(previous behavior).
Fixed #
- Race condition where subcollection repos activated before their parent
document was server-confirmed during first-time anonymous sign-in. The
snapshot listener would hit
PERMISSION_DENIEDand die permanently — writes went through to Firestore but the UI never updated.
1.7.1 #
Changed #
- Updated README with documentation for direct write methods, error handling
table, and
onErrorcallback usage example. - Added one-shot
_resizeWindowcoverage tests for both collection repository types.
1.7.0 #
Added #
onErrorcallback on all three repository constructors (FirestoreDocRepository,FirestoreCollectionRepository,FirestoreCollectionGroupRepository). Called with the error and stack trace when a Firestore snapshot listener or one-shot fetch fails. Optional and non-breaking — when omitted, existing behavior is unchanged.FirewatchErrorHandlertypedef exported fromfirewatch.dartfor typing the callback:void Function(Object error, StackTrace stackTrace).
1.6.0 #
Added #
- Direct write methods on
FirestoreCollectionRepository:addDirect,setDirect,patchDirect,updateDirect,deleteDirect. These bypass theCommandsingle-execution guard, allowing concurrent writes to different documents in the same collection. Use them when rapidly editing multiple items (e.g. toggling checkboxes in a list) where the Command-based methods would silently drop overlapping calls. - Direct write methods on
FirestoreCollectionGroupRepository:setDirect,patchDirect,updateDirect,deleteDirect. Same concurrent-safe semantics for collection group repositories. - Existing Command-based CRUD (
patch,set,update,delete,add) remains unchanged for use cases that benefit fromisRunning/errorsobservability.
1.5.2 #
Fixed #
- In-flight async ops update disposed notifier (#15):
FirestoreCollectionRepository.dispose()did not increment the epoch counter, so pending cache primes, snapshot callbacks, or one-shot fetches could write to already-disposedValueNotifiers (causing Flutter assertion errors). Now mirrorsFirestoreDocRepository.dispose()by bumping_epochfirst.
1.5.1 #
Fixed #
readyreturns stale null afterauthUidchange (#13):_readyCompleterwas never reset, soreadycached its first result forever. NowhasInitializedresets tofalseand a freshCompleteris created on every auth transition, so callers re-await fresh data.
1.5.0 #
Added #
- Batch CRUD operations on
FirestoreCollectionRepository:batchAdd,batchSet,batchPatch,batchUpdate,batchDelete. All areCommandinstances (not plain Futures), so consumers can watchisRunning, listen toerrors, and use the full Command lifecycle — consistent with single-item CRUD commands. - Automatically chunks operations at the Firestore 500-operation batch limit.
- Auth-gated: batch commands route a
StateErrorthrough.errorswhen the UID is null, matching single-item command behavior.
1.4.0 #
Added #
hasInitialized(ValueNotifier<bool>) onFirestoreDocRepository— flips totrueafter the first successful load (from cache or server) and never reverts. Mirrors the existing property onFirestoreCollectionRepository.ready(Future<T?>) onFirestoreDocRepository— completes with the first loaded value (which may benullif the document doesn't exist). Useful for one-timeawaitin services that need data before proceeding.
Fixed #
dispose()now increments the epoch counter to prevent in-flight async operations from writing to a disposed notifier.
1.3.1 #
Fixed #
- Web compatibility:
parentIdinjection no longer crashes on web.cloud_firestore_webthrows an Expando error when calling.parenton a top-levelCollectionReference(where the parent isnull). The newparentIdOf()helper wraps the call in a try-catch, returningnullfor top-level collections.
1.3.0 #
Added #
parentIdis now automatically injected into the data map before callingfromJsonacross all three repository types. Models can opt-in by declaring aparentIdfield in theirfromJsonfactory — no changes toJsonModelrequired. Particularly useful for collection group queries where documents with the same ID can live under different parents.
1.2.0 #
Added #
FirestoreCollectionGroupRepository<T>— reactive queries across all subcollections with the same name via FirestorecollectionGroup(). Supports live pagination, per-item notifiers keyed by full document path, and path-based CRUD (set,update,patch,delete).QueryRefBuildertypedef andGroupPatchrecord type for collection group write operations- Updated README with collection group examples
Fixed #
FirestoreDocRepositorystream subscription now has anonErrorhandler. Previously a stream error (permission denied, network failure) would leaveisLoadingstuck attrueforever.
1.1.0 #
Added #
- Repositories now work without
authUidfor public/unauthenticated collections (e.g.static/config). OmittingauthUidqueries Firestore immediately instead of waiting for a signed-in user. - Updated doc comments with public-collection usage examples
1.0.0 #
- Stable release — no API changes, just documentation polish
- Added doc comments to all public members across both repository types
0.3.0 #
Improved #
- Collection repo now primes UI from Firestore local cache before starting the live subscription, giving instant data on revisits
- Incremental snapshot processing via
docChanges— only re-parses added/modified/removed documents instead of deserializing the full list on every snapshot event - Per-item notifiers are now pruned (removed from the map) when documents leave the snapshot, preventing unbounded memory growth over long sessions
0.2.0 #
Breaking #
- Bumps
command_itfrom ^8.0.0 to ^9.0.0 - Requires Dart >=3.8.0 and Flutter >=3.32.0
writecommand onFirestoreDocRepositoryno longer accepts an unreachablemergeparameter (always merges)
Fixed #
- Race condition in
FirestoreDocRepository._swapon rapid auth changes (added epoch guard) _resizingflag inFirestoreCollectionRepositorycould get permanently stuck, breakingloadMore()- Deleted documents now correctly clear
valuetonullinFirestoreDocRepository - Pagination limit now resets on query/dependency changes
- Per-item notifiers are nulled out when documents leave the snapshot
- All
Commandobjects are now properly disposed
Improved #
- Bumps
cloud_firestoreto ^6.0.0,flutter_lintsto ^6.0.0 - Fixes CI docs workflow (uses stable Flutter channel, gh-pages v4)
- Test coverage increased from 4 to 26 tests (94% line coverage)
0.1.4 #
- Adds
paginateoption toFirestoreCollectRepositoryto enable pagination of collection queries
0.1.3 #
- Improves example and README
0.1.2 #
- Formatting issue
0.1.1 #
- Adds better documentation, examples, and updates licence
0.1.0 #
- Initial release: single-doc and collection repositories
- Live-cache prime, metadata-churn squash
- Live-window pagination, per-item notifiers
- Simple CRUD commands