firebase-ios-sdk
firebase-ios-sdk copied to clipboard
Cache snapshot listener prevents getDocuments() from fetching most recent data
Description
Adding a snapshot listener with SnapshotListenOptions().withSource(.cache) to a collection causes subsequent gets to return old data.
Extra notes:
- Not adding the listener causes normal behaviour. All calls to
getDocuments()return most recent data. - From the debug log, I can see that the second call to
getDocuments()does not print12.6.0 - [FirebaseFirestore][I-FST000001] Using full collection scan to execute query: Query(canonical_id=users|f:|ob:__name__asc)when the cache listener is in place. - A workaround I found is to use a more complex query, ie:
Firestore.firestore().collection("users").order(by: "name"). Adding order to the query fixes the issue.
Reproducing the issue
- Initialize a basic Firebase project with Firestore configured. Nothing fancy here, just a bare bones setup.
- Run the Emulator. (I am running with:
firebase emulators:start --project demo-123) - Using the Emulator UI, add a users collection, and a user document with a name field.
- Run the iOS app (sample code below)
import UIKit
import FirebaseFirestore
class ViewController: UIViewController {
private var cacheListenerRegistration: ListenerRegistration?
override func viewDidLoad() {
super.viewDidLoad()
subscribeToCache()
}
private func subscribeToCache() {
let options = SnapshotListenOptions().withSource(.cache)
cacheListenerRegistration = Firestore.firestore().collection("users").addSnapshotListener(options: options) { querySnapshot, error in
}
}
@IBAction func onFetchFromServer() {
print("onFetchFromServer")
Firestore.firestore().collection("users").getDocuments(source: .server) { (querySnapshot, error) in
if let error {
print("Error getting documents: \(error)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) hasPendingWrites=\(document.metadata.hasPendingWrites) isFromCache=\(document.metadata.isFromCache) \(document.data())")
}
}
}
}
}
- Invoke
onFetchFromServer(). The first time this is called it will fetch the correct data from the server.nBWNES6LPP6NlDC5i7Vd hasPendingWrites=false isFromCache=false ["name": Matt] - Using the Emulator UI, update the name field in the document.
- Invoke
onFetchFromServer()again. This will fetch outdated data but show isFromCache = false.nBWNES6LPP6NlDC5i7Vd hasPendingWrites=false isFromCache=false ["name": Matt]
Firebase SDK Version
12.6
Xcode Version
16.4
Installation Method
Swift Package Manager
Firebase Product(s)
Firestore
Targeted Platforms
iOS
Relevant Log Output
12.6.0 - [FirebaseCore][I-COR000001] Configuring the default app.
12.6.0 - [FirebaseFirestore][I-FST000001] Initializing. Current user:
12.6.0 - [FirebaseFirestore][I-FST000001] Using full collection scan to execute query: Query(canonical_id=users|f:|ob:__name__asc)
12.6.0 - [FirebaseFirestore][I-FST000001] RemoteStore 313039353137623530 restarting streams as connectivity changed
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) stop
12.6.0 - [FirebaseFirestore][I-FST000001] WriteStream (313039313165623038) stop
12.6.0 - [FirebaseCore][I-COR000033] Data Collection flag is not set.
void * _Nullable NSMapGet(NSMapTable * _Nonnull, const void * _Nullable): map table argument is NULL
onFetchFromServer
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) start
12.6.0 - [FirebaseFirestore][I-FST000001] Creating Firestore stub.
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) watch: <ListenRequest 0x7000052e1548>: {
database: "projects/demo-123/databases/(default)"
add_target {
query {
parent: "projects/demo-123/databases/(default)/documents"
structured_query {
from {
collection_id: "users"
}
order_by {
field {
field_path: "__name__"
}
direction: ASCENDING
}
}
}
target_id: 2
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x70000546a5d8>: {
target_change {
target_change_type: ADD
target_ids: 2
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x7000053e75d8>: {
document_change {
document {
name: "projects/demo-123/databases/(default)/documents/users/nBWNES6LPP6NlDC5i7Vd"
fields {
key: "name"
value {
string_value: "Matt"
}
}
create_time {
seconds: 1765308073
nanos: 432541000
}
update_time {
seconds: 1765314956
nanos: 427773000
}
}
target_ids: 2
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x7000053e75d8>: {
target_change {
target_change_type: CURRENT
target_ids: 2
resume_token: "\n\t\010\351\305\205\334\266\261\221\003"
read_time {
seconds: 1765314966
nanos: 676201000
}
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x7000053e75d8>: {
target_change {
resume_token: "\n\t\010\241\306\205\334\266\261\221\003"
read_time {
seconds: 1765314966
nanos: 676257000
}
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) unwatch: <ListenRequest 0x7000053e7548>: {
database: "projects/demo-123/databases/(default)"
remove_target: 2
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x7000053e75d8>: {
target_change {
target_change_type: REMOVE
target_ids: 2
}
}
nBWNES6LPP6NlDC5i7Vd hasPendingWrites=false isFromCache=false ["name": Matt]
onFetchFromServer
nBWNES6LPP6NlDC5i7Vd hasPendingWrites=false isFromCache=false ["name": Matt]
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) watch: <ListenRequest 0x700005881a08>: {
database: "projects/demo-123/databases/(default)"
add_target {
query {
parent: "projects/demo-123/databases/(default)/documents"
structured_query {
from {
collection_id: "users"
}
order_by {
field {
field_path: "__name__"
}
direction: ASCENDING
}
}
}
resume_token: "\n\t\010\241\306\205\334\266\261\221\003"
target_id: 2
expected_count {
}
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) unwatch: <ListenRequest 0x700005882548>: {
database: "projects/demo-123/databases/(default)"
remove_target: 2
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x7000053e75d8>: {
target_change {
target_change_type: ADD
target_ids: 2
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x70000546a5d8>: {
target_change {
target_change_type: RESET
target_ids: 2
resume_token: "\n\t\010\310\262\336\335\266\261\221\003"
read_time {
seconds: 1765314970
nanos: 229064000
}
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x70000546a5d8>: {
document_change {
document {
name: "projects/demo-123/databases/(default)/documents/users/nBWNES6LPP6NlDC5i7Vd"
fields {
key: "name"
value {
string_value: "Matty"
}
}
create_time {
seconds: 1765308073
nanos: 432541000
}
update_time {
seconds: 1765314968
nanos: 713493000
}
}
target_ids: 2
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x70000546a5d8>: {
target_change {
target_change_type: CURRENT
target_ids: 2
resume_token: "\n\t\010\362\263\336\335\266\261\221\003"
read_time {
seconds: 1765314970
nanos: 229234000
}
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x70000546a5d8>: {
target_change {
resume_token: "\n\t\010\304\264\336\335\266\261\221\003"
read_time {
seconds: 1765314970
nanos: 229316000
}
}
}
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) headers (allowlisted):
12.6.0 - [FirebaseFirestore][I-FST000001] WatchStream (313039353232333238) response: <ListenResponse 0x70000546a5d8>: {
target_change {
target_change_type: REMOVE
target_ids: 2
}
}
If using Swift Package Manager, the project's Package.resolved
Expand Package.resolved snippet
{
"originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "087bb95235f676c1a37e928769a5b6645dcbd325",
"version" : "12.6.0"
}
},
{
"identity" : "google-ads-on-device-conversion-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
"revision" : "35b601a60fbbea2de3ea461f604deaaa4d8bbd0c",
"version" : "3.2.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "c2d59acf17a8ba7ed80a763593c67c9c7c006ad1",
"version" : "12.5.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
"version" : "1.69.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "c756a29784521063b6a1202907e2cc47f41b667c",
"version" : "4.5.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "c169a5744230951031770e27e475ff6eefe51f9d",
"version" : "1.33.3"
}
}
],
"version" : 3
}
If using CocoaPods, the project's Podfile.lock
No response
I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.