I am using Firestore to build chat between multiple projects using this Doc
i.e. I have 2 applications / Firebase Projects.
App A and App B
Now I have implemented simple collection in Firestore of App A, it works fine without any issues.
My problem is when I want to access Firestore of App A from App B using the above documentation
I am getting error Error Domain=FIRFirestoreErrorDomain Code=7 "Missing or insufficient permissions
I have attached image of collection
Rules are
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
The Code I am using to configure on ChatViewController of App B is as follows
let secondaryOptions = FirebaseOptions(googleAppID: "1:27992087142:ios:2a4732a34787067a",
gcmSenderID: "27992087142")
secondaryOptions.apiKey = "AIzaSyBicqfAZPvMgC7NZkjayUEsrepxuXzZDsk"
secondaryOptions.projectID = "projectid-12345"
secondaryOptions.bundleID = "com.google.firebase.devrel.FiroptionConfiguration"
secondaryOptions.databaseURL = "https://myproject.firebaseio.com"
guard let secondary = FirebaseApp.app(name: "secondary")
else { assert(false, "Could not retrieve secondary app") }
// Retrieve a Real Time Database client configured against a specific app.
let _ = Database.database(app: secondary)
var collectionReference:CollectionReference?
collectionReference =
Firestore.firestore().collection("eclinic").document("1002").collection("chats")
collectionReference?.order(by: "timestamp", descending: false).addSnapshotListener { [self] (snapShot, err) in
if let error = err {
print("Error \(error)")
}else{
guard let snapDoc = snapShot?.documents else {
print("Return ")
return
}
}
}
Exploring this documentation link I was able to get over my problem, the code is as follows
let secondaryOptions = FirebaseOptions(googleAppID: "1:27992087142:ios:2a4732a34787067a",
gcmSenderID: "27992087142")
secondaryOptions.apiKey = "AIzaSyBicqfAZPvMgC7NZkjayUEsrepxuXzZDsk"
secondaryOptions.projectID = "projectid-12345"
secondaryOptions.bundleID = "com.google.firebase.devrel.FiroptionConfiguration"
secondaryOptions.databaseURL = "https://myproject.firebaseio.com"
FirebaseApp.configure(name: "secondary", options: secondaryOptions)
// Retrieve a Real Time Database client configured against a specific app.
guard let secondary = FirebaseApp.app(name: "secondary")
else { assert(false, "Could not retrieve secondary app") }
let firestoreSecondary = Firestore.firestore(app: secondary)
var collectionReference:CollectionReference?
collectionReference =
firestoreSecondary.collection("eclinic").document("1002").collection("chats")
collectionReference?.order(by: "timestamp", descending: false).addSnapshotListener { [self] (snapShot, err) in
if let error = err {
print("Error \(error)")
}else{
guard let snapDoc = snapShot?.documents else {
print("Return ")
return
}
}
}
Related
I am using Firebase as my backend service & fetching data using the SnapshotListener ( which keep listening for any data changes ). I want to remove that listener (only run and fetch data on first load).
private func loadShops(_ category: String) {
db.collection(category).addSnapshotListener { snapshot, error in
guard error == nil else {
print(error!.localizedDescription)
return
}
if let docs = snapshot?.documents {
for doc in docs {
self.shops = []
let shopData = doc.data()
var newShop = Shop()
newShop.shopName = shopData["name"] as? String ?? "Empty Name"
self.shops.append(newShop)
self.shops.shuffle()
}
self.shopsView.tableView.reloadData()
}
}
}
Based on the API example above - I'm assuming you are using the Firestore API and not the older Firebase.
If its firestore, you could just use query fetch instead of using a snapshot listener.
Refer to https://firebase.google.com/docs/firestore/query-data/queries#execute_a_query
private func loadShops(_ category: String) {
db.collection(category).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else if let querySnapshot = querySnapshot {
for document in querySnapshot.documents {
print("\(document.documentID) => \(document.data())")
// do the conversion to model object.
}
}
}
Using the snapshot listener is only if you want to continue monitoring the cloud for new documents. It is also costly as per the pricing documentation.
I have implemented pagination firestore query to return results 5 at a time.
It first starts by creating a listener and pulling 5 results. On the pull to refresh it pulls 5 more results. On the 2nd pull to refresh it for some reason pulls 10 results. On the 3rd pull to refresh it doesn't pull any results. After looking at the data some data is missing.
Below is my MessageListener function:
func createMessageListener() {
reference = db.collection(["chats", channel.id!, "messages"].joined(separator: "/"))
let first = reference?.order(by: "created", descending: true).limit(to: 5)
messageListener = first!.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
return
}
snapshot.documentChanges.forEach { change in
self.handleDocumentChange(change)
self.updateReadStatus()
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
self.hasMoreMessages = false
return
}
self.last = self.reference?.order(by: "created", descending: true).start(afterDocument: lastSnapshot).limit(to: 5)
}
}
Below is my loadMoreMessages function:
func loadMoreMessages() {
if hasMoreMessages {
last!.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
self.hasMoreMessages = false
self.delegate.viewModelDidLoadMoreMessages(hasMoreMessages: self.hasMoreMessages)
return
}
self.moreMessagesLoaded = true
snapshot.documentChanges.forEach { change in
self.handleDocumentChange(change)
}
self.delegate.viewModelDidLoadMoreMessages(hasMoreMessages: true)
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
self.hasMoreMessages = false
return
}
self.last = self.reference?.start(afterDocument: lastSnapshot)
}
} else {
self.delegate.viewModelDidLoadMoreMessages(hasMoreMessages: self.hasMoreMessages)
}
}
Not too sure what is going on. If in the create listener function i change the first limit to 35 for example. It will pull all the data.
Thanks in advance for any help
When I run my firestore code , beginning it is ok. When it comes to running with simulators or my new phone, it goes to this exception regardless of my firestore version :
Some said that it is related to the pen issues but this is not gonna work.Am I not encouraged to use anonymous login ?
Here is my code :
Auth.auth().signInAnonymously() { (result , error) in
guard let authResult = result else {
return
}
let user = authResult.user
let isAnonymous = user.isAnonymous // true
if(isAnonymous){
let uid = user.uid
User.shared.userId = uid
print("user id : \(User.shared.userId)")
let userRef = db.collection("user").document(User.shared.userId)
userRef.getDocument { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if (querySnapshot?.get("progress") == nil) {
var dataDict : Dictionary<String , Any> = [:]
dataDict["progress"] = 0
userRef.setData(dataDict)
}else{
let dict = querySnapshot?.data()
let sssresult = dict!.filter{ $0.key == "progress" }.first?.value as? Int ?? 0
User.shared.progress = sssresult
}
}
}
}
// Do any additional setup after loading the view.
}
// CocoaPods: try to load from the gRPC-C++ Framework.
NSBundle* _Nullable FindGrpcCppFrameworkBundle() {
return [NSBundle bundleWithIdentifier:#"org.cocoapods.grpcpp"];
}
CloudKit Public Records And Changes not Downloaded
I have a CloudKit app with records for both a Public and a Custom Private
Zone. I seem to have the change token process working for the custom
private zone but am unable to get the public data to work. The code I am
using is identical for both databases except for the public/private
names and using the default zone for the public. I understand that
subscriptions do not work on default zones, but I could not find any
references to limitations on change tokens for public data. Xcode 10.1, iOS 12.0
I create my PubicData class and initialize it:
var publicDatabase : CKDatabase!
init() {
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
context = kAppDelegate.context
let container = CKContainer.default()
publicDatabase = container.publicCloudDatabase
}//init
the download function that is called from the app entry scene - a tableview:
func downloadPublicUpdates(finishClosure : # escaping(UIBackgroundFetchResult) -> Void) {
var listRecordsUpdated : [CKRecord] = []
var listRecordsDeleted : [String : String] = [:]
var publicChangeToken : CKServerChangeToken!
var publicChangeZoneToken : CKServerChangeToken!
let userSettings = UserDefaults.standard
if let data = userSettings.value(forKey: "publicChangeToken") as? Data {
if let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass : CKServerChangeToken.self, from : data) {
publicChangeToken = token
print("publicChangeToken exists")
}
} else {
print("userSettings entry for publicChangeToken does not exist")
}//if let data
if let data = userSettings.value(forKey: "publicChangeZoneToken") as? Data {
if let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, from: data) {
publicChangeZoneToken = token
}
}//if let data
let zone = CKRecordZone.default()
var zonesIDs : [CKRecordZone.ID] = [zone.zoneID]
let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: publicChangeToken)
operation.recordZoneWithIDChangedBlock = {(zoneID) in
zonesIDs.append(zoneID)
}
operation.changeTokenUpdatedBlock = {(token) in
publicChangeToken = token
}
operation.fetchDatabaseChangesCompletionBlock = {(token, more, error) in
if error != nil{
finishClosure(UIBackgroundFetchResult.failed)
} else if !zonesIDs.isEmpty {
publicChangeToken = token
let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration()
configuration.previousServerChangeToken = publicChangeZoneToken
let fetchOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zonesIDs, configurationsByRecordZoneID: [zonesIDs[0] : configuration])
fetchOperation.recordChangedBlock = {(record) in
listRecordsUpdated.append(record)
}//fetchOperation.recordChangedBlock
fetchOperation.recordWithIDWasDeletedBlock = {(recordID, recordType) in
listRecordsDeleted[recordID.recordName] = recordType
}//fetchOperation.recordWithIDWasDeletedBlock
fetchOperation.recordZoneChangeTokensUpdatedBlock = {(zoneID, token, data) in
publicChangeZoneToken = token
}//fetchOperation.recordZoneChangeTokensUpdatedBlock
fetchOperation.recordZoneFetchCompletionBlock = {(zoneID, token, data, more, error) in
if let ckerror = error as? CKError {
self.processErrors(error: ckerror)
} else {
publicChangeZoneToken = token
self.updateLocalRecords(listRecordsUpdated : listRecordsUpdated)
self.deleteLocalRecords(listRecordsDeleted : listRecordsDeleted)
listRecordsUpdated.removeAll()
listRecordsDeleted.removeAll()
}//if else
}//fetchOperation.recordZoneFetchCompletionBlock
fetchOperation.fetchRecordZoneChangesCompletionBlock = {(error) in
if error != nil {
print("Error fetchRecordZoneChangesCompletionBlock")
finishClosure(UIBackgroundFetchResult.failed)
} else {
if publicChangeToken != nil {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: publicChangeToken, requiringSecureCoding: false) {
userSettings.set(data, forKey : "publicChangeToken")
}
}//if changeToken != nil
if publicChangeZoneToken != nil {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: publicChangeZoneToken, requiringSecureCoding: false) {
userSettings.set(data, forKey : "publicChangeZoneToken")
}
}
//self.updateInterface()
self.updateLocalReferences()
finishClosure(UIBackgroundFetchResult.newData)
}
}//fetchOperation.fetchRecordZoneChangesCompletionBlock
self.publicDatabase.add(fetchOperation)
} else {//else if !zonesIDs.isEmpty
finishClosure(UIBackgroundFetchResult.noData)
}//if zoneid not empty
}//fetchDatabaseChangesCompletionBlock
print("listRecordsUpdated.count is \(listRecordsUpdated.count)")
publicDatabase.add(operation)
}//downloadPublicUpdates
Outside of class: var PD = PDData()
I call the download method in viewDidLoad from the initial TableViewController:
PD.downloadPublicUpdates { (result) in
print("in ctvc viewDidLoad and downloadPublicUpdates")
switch result {
case .noData:
print("no data")
case .newData:
print("new data")
case .failed:
print("failed to get data")
}//switch
}//downloadPublicUpdates
The console output is always:
userSettings entry for publicChangeToken does not exist
listRecordsUpdated.count is 0
in ctvc viewDidLoad and downloadPublicUpdates
failed to get data
Any guidance would be appreciated.
There are no change tokens available in a public database. Those only exist in private and shared databases.
To keep things in sync, you typically have to keep a modification date on records locally, and then query for stuff that is newer on the CloudKit server using a CKQueryOperation.
Good luck!
So I have done this on my android app (and it works), to populate a list with the document names from a collection
db.collection("usersAuth/${FirebaseAuth.getInstance().uid!!}/KitLists")
.addSnapshotListener(EventListener<QuerySnapshot> { value, e ->
if (e != null) {
Log.w("TAG", "Listen failed.", e)
return#EventListener
}
for (document in value.documents) {
val data = document
val kitName = data.id
firstKitList.add(kitName)
}
mainListViewAdapter.notifyDataSetChanged()
})
I am trying to do the same on my iOS version but I don't know whats wrong
override func viewWillAppear(_ animated: Bool) {
setListener()
}
func setListener() {
db.collection("usersAuth/\(String(describing: Auth.auth().currentUser))/KitLists")
.addSnapshotListener { (snapshot, error ) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
} else {
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
let kitListName = data["KitLists"] as? String
let newLists = KitList(kitListName: kitListName!)
self.lists.append(newLists)
}
self.tableView.reloadData()
}
}
}
any ideas? Thanks
-- EDIT
Firestore
Firestore2
You need to get the uid from the currentUser, for example:
if let userId = Auth.auth().currentUser.uid {
db.collection("usersAuth").document(userId).collection("KitLists")
.addSnapshotListener { (snapshot, error ) in
//...
}
To get the KitLists documentId
for document in snap.documents {
let documentName = document.documentID // <--- This
let newLists = KitList(kitListName: documentName)
self.lists.append(newLists)
}