Repository navigation
Add live read-only subtype collection views - #41
Closed
danReynolds wants to merge 8 commits into
Closed
danReynolds wants to merge 8 commits into
danReynolds wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
It introduces compile-time issues in lib/loon.dart and lib/collection_view.dart (invalid library directive and invalid generic parameter ordering) that must be fixed before it can be safely merged.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR adds a new “view” layer for collections/documents intended to enable type-safe narrowing of snapshot data, wires it into the main loon.dart library, and adjusts broadcast cache invalidation behavior.
Changes:
- Add
CollectionView/DocumentView/DocumentSnapshotViewtypes to support subtype “views” over existingCollection/DocumentAPIs. - Register the new part file in
lib/loon.dart. - Always invalidate observer cache entries when writing document broadcast events.
File summaries
| File | Description |
|---|---|
| lib/loon.dart | Registers the new collection_view.dart part (but currently introduces an invalid library directive). |
| lib/collection_view.dart | Adds the new view types for type-safe narrowing of document snapshot data. |
| lib/broadcast_manager.dart | Changes cache invalidation behavior on writeDocument and updates nearby logic/comments. |
| example/pubspec.lock | Updates resolved dependency versions and SDK constraint in the example lockfile. |
Review details
Suppressed comments (2)
lib/collection_view.dart:28
- This class has the same invalid type-parameter ordering issue (
S extends TreferencesTbefore it is declared). Swap the type parameter order and update the return type / constructor call accordingly.
class DocumentView<S extends T, T> {
final Document<T> doc;
DocumentView(this.doc);
DocumentSnapshotView<S, T>? get() {
final snap = doc.get();
if (snap == null) {
return null;
}
return DocumentSnapshotView(snap);
}
lib/collection_view.dart:39
- This class also uses the invalid type-parameter ordering (
S extends T, T). Swap the type parameter order and update thedoc()return type / constructor call to match the updatedDocumentViewdeclaration.
class CollectionView<S extends T, T> {
final Collection<T> _collection;
CollectionView(this._collection);
DocumentView<S, T> doc(String id) {
return DocumentView(_collection.doc(id));
}
- Files reviewed: 3/4 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @@ -0,0 +1,40 @@ | |||
| part of 'loon.dart'; | |||
|
|
|||
| class DocumentSnapshotView<S extends T, T> { | |||
| @@ -1,4 +1,4 @@ | |||
| library loon; | |||
| library; | |||
Comment on lines
+106
to
+107
| // All cached observer values for the document and its collection | ||
| // are invalidated after the document is mutated. |
Comment on lines
+31
to
+39
| /// A view of a collection that allows for type-safe narrowing to a specific subtype of the collection's document type. | ||
| class CollectionView<S extends T, T> { | ||
| final Collection<T> _collection; | ||
|
|
||
| CollectionView(this._collection); | ||
|
|
||
| DocumentView<S, T> doc(String id) { | ||
| return DocumentView(_collection.doc(id)); | ||
| } |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
main replaced the asyncEvent helper with flushBroadcasts, so the stream tests now use the same fakeAsync pattern as the rest of the suite. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 29, 2026
* Dispose only the observers stream builders create `observe()` on an observed document or query returns the same observer, so a builder given one was disposing the caller's observer when it was removed or switched sources, closing it for every other listener. The builders now dispose an observer only if they created it. Removal is the case this branch introduced; disposing on a source switch predates it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Construct collection views only through view() The public `CollectionView<S>(collection)` constructor accepted any type, so `CollectionView<String>(animals)` compiled and silently matched nothing. `view<S>()` requires S to extend the collection's type at compile time, and is now the only way to create a view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare collection views by collection and subtype A collection view compared by identity, so a view created inline in `build` made `QueryStreamBuilder` observe and scan the collection again on every rebuild. Views of the same type over the same collection select the same documents, so they now compare equal, as collections compare by path and document views by path and type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give views their own stream builders Accepting views in DocumentStreamBuilder and QueryStreamBuilder changed their callbacks to read-only snapshots for every caller, so existing builders could no longer write through `snap.doc`, and `Queryable.toQuery()` changed type. Those builders keep their signatures from main. DocumentViewStreamBuilder and QueryViewStreamBuilder build from views, and also accept ordinary documents and queries through the read interfaces they implement. With this, the view layer only adds API: no existing signature changes, and the example app is back to writing through `userSnap.doc`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sort a view's projected snapshots instead of projecting in the comparator A view's sort ran in the underlying query, with a comparator that looked up both snapshots' projections on every comparison. The view now keeps its sort and applies it to its projected snapshots, so each snapshot's projection is looked up once per read. In interleaved local runs over 2,000 matching documents, `get()`, reads after a write, and stream emissions were faster in every round, by roughly 2-2.7x at the median. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A mixed
Collection<A>can expose itsBdocuments through a live, read-only view. Other subtypes are excluded rather than returned as snapshots with null data.CollectionView<B>,DocumentView<B>, and their snapshots preserve the existing read API and subtype inference. Snapshot document handles are also read-only. The parent collection owns serialization and writes; subtype changes appear as removals/additions relative to each view.view<S>()enforces a subtype bound at compile time; the constructor filters by runtime data type and returns no matches for unrelated types.Views reuse the existing query engine for filters, sorting, hydration, dependency invalidation, and change events. Document observation uses direct document and event lookup. Weak-key projection caches share typed snapshots across filters, sorting, and reads without a separate invalidation protocol. Observed result lists are unmodifiable so a caller cannot corrupt the shared cached result. Projected streams retain their identity across widget rebuilds.
Review findings addressed:
exists()avoids projecting all matching documents.Compatibility: builder callbacks receive
DocumentSnapshotView<T>, andQueryable.toQuery()returnsQueryView<T>. Direct collection/document methods retain their writable return types. The README, changelog, and example describe using the original collection or document for writes from builders.Validation:
flutter analyze --no-pub: clean.view<S>()rejects unrelated subtype arguments.