Skip to content

feat(firestore): add support for local source snapshot listeners#8929

Merged
mikehardy merged 2 commits into
invertase:mainfrom
astanb:add-source-snapshot-listener-option
May 1, 2026
Merged

feat(firestore): add support for local source snapshot listeners#8929
mikehardy merged 2 commits into
invertase:mainfrom
astanb:add-source-snapshot-listener-option

Conversation

@astanb

@astanb astanb commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Description

Related issues

Release Summary

Adds the ability to specify the source ('cache'/'default') for snapshot listeners.

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
    • Yes
  • My change supports the following platforms;
    • Android
    • iOS
    • Other (macOS, web)
  • My change includes tests;
    • e2e tests added or updated in packages/\*\*/e2e
    • jest tests added or updated in packages/\*\*/__tests__
  • I have updated TypeScript types that are affected by my change.
  • This is a breaking change;
    • Yes
    • No

Test Plan


New tests added for functionality.
All tests for both platforms passing.

Screenshot 2026-03-12 at 17 45 47

🔥


Note

Medium Risk
Changes Firestore listener behavior on both Android and iOS by wiring a new source option into native addSnapshotListener APIs, 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') for DocumentReference.onSnapshot and Query.onSnapshot across the JS, Android, and iOS layers.

Updates JS/TS types and argument parsing (SnapshotListenOptions, new ListenSource) to validate and default the option, and updates native modules to build SnapshotListenOptions/FIRSnapshotListenOptions with 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.

Copilot AI review requested due to automatic review settings March 12, 2026 17:47
@vercel

vercel Bot commented Mar 12, 2026

Copy link
Copy Markdown

@astanb is attempting to deploy a commit to the Invertase Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Mar 12, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 source through 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.

Comment thread packages/firestore/lib/types/firestore.ts Outdated
Comment thread packages/firestore/lib/types/internal.ts Outdated
Comment thread packages/firestore/type-test.ts
Comment on lines +361 to +370
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);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/firestore/ios/RNFBFirestore/RNFBFirestoreDocumentModule.m

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@mikehardy mikehardy added Workflow: Waiting for User Response Blocked waiting for user response. plugin: firestore Firebase Cloud Firestore type: enhancement Implements a new Feature labels Mar 12, 2026
@mikehardy

Copy link
Copy Markdown
Collaborator

Java failed lint and objective-c may as well

Attempt 1
  Error: google-java-format exited with exit code 1.
      at errorFromExitCode (/home/runner/work/react-native-firebase/react-native-firebase/node_modules/google-java-format/index.js:19:10)
      at ChildProcess.<anonymous> (/home/runner/work/react-native-firebase/react-native-firebase/node_modules/google-java-format/index.js:130:14)
      at ChildProcess.emit (node:events:519:28)
      at maybeClose (node:internal/child_process:1101:16)
      at ChildProcess._handle.onexit (node:internal/child_process:304:5)

for java: yarn lint:android and it will error but also write the fixes in ready for commit while checking
for ios: yarn lint:ios:check and/or yarn lint:ios:fix to see and/or write lint fixes in automatically

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 mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/firestore/lib/types/namespaced.ts Outdated
Comment thread packages/firestore/lib/utils/index.ts Outdated
Comment thread packages/firestore/lib/utils/index.ts Outdated
Comment thread packages/firestore/lib/FirestoreDocumentReference.ts Outdated
Comment thread packages/firestore/lib/FirestoreQuery.ts Outdated

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/firestore/lib/utils/index.ts Outdated
Comment thread packages/firestore/lib/utils/index.ts Outdated
@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown

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

@github-actions github-actions Bot added the Stale label Apr 6, 2026
@astanb

astanb commented Apr 6, 2026

Copy link
Copy Markdown
Contributor Author

@mikehardy should be ready for merge now

@mikehardy

Copy link
Copy Markdown
Collaborator

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

@mikehardy mikehardy added Keep Open avoids the stale bot and removed Stale labels Apr 10, 2026
@mikehardy mikehardy force-pushed the add-source-snapshot-listener-option branch from 458f91e to d8a54c8 Compare May 1, 2026 00:43

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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

@mikehardy mikehardy added Workflow: Pending Merge Waiting on CI or similar and removed Workflow: Waiting for User Response Blocked waiting for user response. Needs Attention labels May 1, 2026
astanb and others added 2 commits April 30, 2026 21:19
…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
@mikehardy mikehardy force-pushed the add-source-snapshot-listener-option branch from d8a54c8 to 7541dec Compare May 1, 2026 02:19
@mikehardy

Copy link
Copy Markdown
Collaborator

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.

@mikehardy

Copy link
Copy Markdown
Collaborator

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

@mikehardy mikehardy merged commit f973088 into invertase:main May 1, 2026
21 of 23 checks passed
@mikehardy mikehardy removed Keep Open avoids the stale bot Workflow: Pending Merge Waiting on CI or similar labels May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin: firestore Firebase Cloud Firestore type: enhancement Implements a new Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement ListenSource / SnapshotListenOptions for firestore (new SDK feature March 2024)

4 participants