firewatch 1.13.0 copy "firewatch: ^1.13.0" to clipboard
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-ops run() while a previous execution is in flight (and runAsync() 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 a writeAckPolicy constructor parameter; with an ackGrace set (~1–2s recommended), every write Command, *Direct write, 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 existing onError handler — 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) on FirestoreCollectionRepository. 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 over add/addDirect in new code.

Changed #

  • add/addDirect now mint the document ID locally (exactly what CollectionReference.add does internally) and run the underlying set under the repo's writeAckPolicy, 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. addDirect is now an alias of create.

1.12.0 #

Added #

  • lastError / hasError on list repositories. Collection and collection-group repos now expose ValueNotifier<Object?> lastError (and a hasError convenience 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).
  • isFromCache on list repositories. Mirrors the latest snapshot's metadata.isFromCache. With Firestore persistence enabled (notably on web), an offline get() 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; a subscribe: false repo gave up on the first .get() failure and — worse — still flipped hasInitialized to true over an empty value, 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 surfaces lastError once the budget is exhausted.

Changed #

  • showEmpty now also requires !hasError. A failed fetch leaves the list empty too; showEmpty no longer reports true for it.

1.11.0 #

Changed #

  • refresh() is now a soft refresh. It no longer clears value or resets hasInitialized to false while the re-fetch is in flight. The already-loaded list stays visible (and isRefreshing is true, isInitializing stays false) 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 populate value. The internal _swap gained a preserveInitialized flag to support this; setQuery, auth/dependency changes, and the initial start() 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 a loadMore during 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.

  • hasMore is now false when paginate: false. Previously a non-paginated repo with more documents than pageSize left hasMore stuck true, even though the snapshot already held the full result set and loadMore() did nothing useful.

  • FirestoreDocRepository.ready no longer hangs when the repo is disposed before its first load completes. dispose() now completes the ready future (with the current value) so a pending await repo.ready resolves 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, goes null when 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 FirestoreDocRepository write 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 passes uid but 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 #

  • FirestoreCollectionGroupRepository safety 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_DENIED error loop on sign-out).
    • The stream onError callbacks 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 to onError.

1.10.0 #

Added #

  • refresh() on FirestoreDocRepository. A public method to force a re-read of the document using the current auth state, mirroring the existing FirestoreCollectionRepository.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, keeping value and hasInitialized until fresh data arrives, so no loading / uninitialized state flashes over the already-loaded document.

Fixed #

  • One-shot FirestoreDocRepository now clears value when a refresh() 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>) — like patch, but uses set(..., 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 patch keeps its update() semantics (throws not-found on missing doc) for callers that rely on that.
  • Direct writes on FirestoreDocRepository, mirroring the existing FirestoreCollectionRepository Direct API:
    • writeDirect(T)set(merge: true) with full model
    • updateDirect(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_DENIED errors 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, and onError suppresses 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_DENIED because 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 FirestoreCollectionRepository constructor 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 maxRetries exhausted, the repo settles into hasInitialized = 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_DENIED and 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 onError callback usage example.
  • Added one-shot _resizeWindow coverage tests for both collection repository types.

1.7.0 #

Added #

  • onError callback 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.
  • FirewatchErrorHandler typedef exported from firewatch.dart for 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 the Command single-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 from isRunning/errors observability.

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-disposed ValueNotifiers (causing Flutter assertion errors). Now mirrors FirestoreDocRepository.dispose() by bumping _epoch first.

1.5.1 #

Fixed #

  • ready returns stale null after authUid change (#13): _readyCompleter was never reset, so ready cached its first result forever. Now hasInitialized resets to false and a fresh Completer is 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 are Command instances (not plain Futures), so consumers can watch isRunning, listen to errors, 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 StateError through .errors when the UID is null, matching single-item command behavior.

1.4.0 #

Added #

  • hasInitialized (ValueNotifier<bool>) on FirestoreDocRepository — flips to true after the first successful load (from cache or server) and never reverts. Mirrors the existing property on FirestoreCollectionRepository.
  • ready (Future<T?>) on FirestoreDocRepository — completes with the first loaded value (which may be null if the document doesn't exist). Useful for one-time await in 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: parentId injection no longer crashes on web. cloud_firestore_web throws an Expando error when calling .parent on a top-level CollectionReference (where the parent is null). The new parentIdOf() helper wraps the call in a try-catch, returning null for top-level collections.

1.3.0 #

Added #

  • parentId is now automatically injected into the data map before calling fromJson across all three repository types. Models can opt-in by declaring a parentId field in their fromJson factory — no changes to JsonModel required. 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 Firestore collectionGroup(). Supports live pagination, per-item notifiers keyed by full document path, and path-based CRUD (set, update, patch, delete).
  • QueryRefBuilder typedef and GroupPatch record type for collection group write operations
  • Updated README with collection group examples

Fixed #

  • FirestoreDocRepository stream subscription now has an onError handler. Previously a stream error (permission denied, network failure) would leave isLoading stuck at true forever.

1.1.0 #

Added #

  • Repositories now work without authUid for public/unauthenticated collections (e.g. static/config). Omitting authUid queries 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_it from ^8.0.0 to ^9.0.0
  • Requires Dart >=3.8.0 and Flutter >=3.32.0
  • write command on FirestoreDocRepository no longer accepts an unreachable merge parameter (always merges)

Fixed #

  • Race condition in FirestoreDocRepository._swap on rapid auth changes (added epoch guard)
  • _resizing flag in FirestoreCollectionRepository could get permanently stuck, breaking loadMore()
  • Deleted documents now correctly clear value to null in FirestoreDocRepository
  • Pagination limit now resets on query/dependency changes
  • Per-item notifiers are nulled out when documents leave the snapshot
  • All Command objects are now properly disposed

Improved #

  • Bumps cloud_firestore to ^6.0.0, flutter_lints to ^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 paginate option to FirestoreCollectRepository to 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
2
likes
160
points
116
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Lightweight Firestore repositories for Flutter: single-doc, collection, and collection-group repos that react to auth, stream updates, and support live-window pagination.

Repository (GitHub)
View/report issues
Contributing

Topics

#firestore #repository #state-management #pagination #flutter

License

MIT (license)

Dependencies

cloud_firestore, command_it, flutter

More

Packages that depend on firewatch