firebase-ios-sdk icon indicating copy to clipboard operation
firebase-ios-sdk copied to clipboard

Cache snapshot listener prevents getDocuments() from fetching most recent data

Open mattwiechec opened this issue 1 week ago • 1 comments

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 print 12.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

  1. Initialize a basic Firebase project with Firestore configured. Nothing fancy here, just a bare bones setup.
  2. Run the Emulator. (I am running with: firebase emulators:start --project demo-123)
  3. Using the Emulator UI, add a users collection, and a user document with a name field. Image
  4. 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())")
                }
            }
        }
    }
}
  1. Invoke onFetchFromServer(). The first time this is called it will fetch the correct data from the server.
    nBWNES6LPP6NlDC5i7Vd hasPendingWrites=false isFromCache=false ["name": Matt]
    
  2. Using the Emulator UI, update the name field in the document. Image
  3. 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

mattwiechec avatar Dec 09 '25 21:12 mattwiechec

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

google-oss-bot avatar Dec 09 '25 21:12 google-oss-bot