feat(firestore): add support for local source snapshot listeners#8929
Conversation
|
@astanb is attempting to deploy a commit to the Invertase Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Adds support for configuring Firestore snapshot listeners to listen from a specific source ('cache' or 'default'), aligning React Native Firebase behavior with newer Firestore SDK capabilities and addressing #8126.
Changes:
- Extend snapshot listener option parsing + validation to accept
source: 'default' | 'cache'. - Plumb
sourcethrough to native snapshot listener registrations on iOS and Android. - Update TS types and add/extend Jest + e2e coverage for the new option (including invalid values).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/firestore/lib/utils/index.ts | Parses SnapshotListenOptions.source, defaults it, and validates allowed values. |
| packages/firestore/lib/types/firestore.ts | Adds source?: 'default' | 'cache' to modular SnapshotListenOptions typing. |
| packages/firestore/lib/types/namespaced.ts | Updates namespaced docs/types for listener options (adds source, makes includeMetadataChanges optional). |
| packages/firestore/lib/types/internal.ts | Extends internal listener options type to include source. |
| packages/firestore/lib/FirestoreDocumentReference.ts | Passes parsed source option through document onSnapshot to native. |
| packages/firestore/lib/FirestoreQuery.ts | Passes parsed source option through query onSnapshot to native (incl. named queries). |
| packages/firestore/ios/RNFBFirestore/RNFBFirestoreDocumentModule.{h,m} | Uses FIRSnapshotListenOptions with source + includeMetadataChanges for document listeners. |
| packages/firestore/ios/RNFBFirestore/RNFBFirestoreCollectionModule.{h,m} | Uses FIRSnapshotListenOptions with source + includeMetadataChanges for query listeners. |
| packages/firestore/android/.../ReactNativeFirebaseFirestoreDocumentModule.java | Uses SnapshotListenOptions.Builder to set ListenSource + metadata changes for document listeners. |
| packages/firestore/android/.../ReactNativeFirebaseFirestoreCollectionModule.java | Uses SnapshotListenOptions.Builder to set ListenSource + metadata changes for query listeners. |
| packages/firestore/e2e/DocumentReference/onSnapshot.e2e.js | Adds e2e coverage for invalid source and cache-source document listeners. |
| packages/firestore/e2e/Query/onSnapshot.e2e.js | Adds e2e coverage for invalid source and cache-source query listeners. |
| packages/firestore/tests/firestore.test.ts | Adds unit tests for parseSnapshotArgs handling of source. |
| packages/firestore/type-test.ts | Updates type tests to exercise source: 'cache' on snapshot listeners. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| await docRef.set({ enabled: true }); | ||
| await docRef.get(); | ||
|
|
||
| let unsub = () => {}; | ||
| try { | ||
| await firebase.firestore().disableNetwork(); | ||
| const callback = sinon.spy(); | ||
| unsub = docRef.onSnapshot({ source: 'cache' }, callback); | ||
| await Utils.spyToBeCalledOnceAsync(callback); | ||
| callback.args[0][0].metadata.fromCache.should.equal(true); |
There was a problem hiding this comment.
I love the added test - thank you - I wonder if this truly probes it though - I think it would be false-positive in that ListenSource.default and ListenSource.cache would have the same behavior in this scenario wouldn't they (since default listens to server and cache) ? So this doesn't really differentiate if the implementation is correct
The only thing I can think of that would truly differentiate is to have a function outside of the test app update firestore (so it didn't go in the test app local cache during set) and then have two listeners with the two different sources, do the out-of-band server set and make sure server listener was called but cache was not. Then do an in-band set and make sure they were both called
Cloud function to run in emulator could be added in to this area https://github.com/invertase/react-native-firebase/blob/main/.github/workflows/scripts/functions/src/index.ts ) where you told it a document path to update and a key/value pair perhaps (or anything that worked) via a post from the test app, and post from the test app could look like the auth ones do https://github.com/invertase/react-native-firebase/blob/main/packages/auth/e2e/helpers.js
A bit heavy but it is the only idea I have to really differentiate between the two source options and prove they work as defined vs just appear to function without error
Thoughts?
There was a problem hiding this comment.
Good point, the existing test didn't really prove anything!
Instead of going the callable function route I added an out-of-band helper function that uses direct API access to firestore, adds a little boilerplate but avoids the dependency on functions and could potentially be re-used in the future. Let me know if you'd prefer me to go the callable function route and I can do that.
mikehardy
left a comment
There was a problem hiding this comment.
The implementation here looks really solid at the native level
The typescript types have a request for more strict coherence with upstream as mentioned in comments
The test support in types and unit look great, e2e I have a false-positive worry but solving it looks annoyingly "heavy", there is prior art to copy though so is perhaps not a large task?
This looks like just a little polish away from being merge-able though and I imagine it is already working in practice, fantastic, thank you
|
Java failed lint and objective-c may as well for java: We're strict on formatting so it doesn't turn into an opinion battle but the fixes are all automated at least so shouldn't require thought except how to run them |
mikehardy
left a comment
There was a problem hiding this comment.
that e2e test is exactly what I was hoping for, fantastic
It is done IMHO to just the last places to thread the ListenSource type change through then the firestore package here has a great new feature
mikehardy
left a comment
There was a problem hiding this comment.
this looks fantastic - I believe it is down to two remaining spots still missing the requested ListenSource type addition, and that's it ? I think ready for merge after that
|
Hello 👋, this PR has been opened for more than 14 days with no activity on it. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically |
|
@mikehardy should be ready for merge now |
|
We have done a lot of work on types in firestore since this was worked on, and that's not a bad thing - I can fix this one up and get it merged in, I'd like to see it released and I should be able to rebase main into it quickly |
458f91e to
d8a54c8
Compare
mikehardy
left a comment
There was a problem hiding this comment.
- squashed to a single feature commit after running all lints
- rebased to main and fixed up conflicts
- updated new-since-this-PR compare-types script to recognize stale entries as failures, and fixed up the now-stale entries for ListenSource since they are implemented now
This passes yarn test:full locally which is every test we have, running in CI now
this should be good to go, thanks again for the feature, the excellent e2e testing, and the patience
…listeners - create ListenSource type to maintain parity with JS SDK - add test helper for out-of-band writes for local / server e2e testing
noticed that despite ListenSource being a fresh implementation that fixed a gap between firebase-js-sdk and react-native-firebase that yarn compare:types was still green it should have failed and notified us that we had stale entries to clean up, now it will
d8a54c8 to
7541dec
Compare
|
not sure how an iOS compile bug snuck in there when I swear I had this testing locally and working before, but pushed a fix/tweak regardless. |
|
iOS simulators are very flaky at the moment for reasons unrelated to this PR, seeing one of the two go green (and with tests passing locally) this is good to go |
Description
Related issues
Release Summary
Adds the ability to specify the source ('cache'/'default') for snapshot listeners.
Checklist
AndroidiOSOther(macOS, web)e2etests added or updated inpackages/\*\*/e2ejesttests added or updated inpackages/\*\*/__tests__Test Plan
New tests added for functionality.
All tests for both platforms passing.
🔥
Note
Medium Risk
Changes Firestore listener behavior on both Android and iOS by wiring a new
sourceoption into nativeaddSnapshotListenerAPIs, which could affect realtime update delivery and caching semantics. Coverage is improved with new unit and e2e tests, but it still touches core data subscription paths.Overview
Adds support for specifying snapshot listener
source('default'or'cache') forDocumentReference.onSnapshotandQuery.onSnapshotacross the JS, Android, and iOS layers.Updates JS/TS types and argument parsing (
SnapshotListenOptions, newListenSource) to validate and default the option, and updates native modules to buildSnapshotListenOptions/FIRSnapshotListenOptionswith the requested source. Expands Jest/type tests and adds emulator e2e coverage (including out-of-band server writes) to verify cache-only vs default listener behavior, and tightens the type-compare tooling to fail on stale registry/config entries (with clearer reporting).Reviewed by Cursor Bugbot for commit 7541dec. Bugbot is set up for automated code reviews on this repo. Configure here.