How can I get Document Data and Reference Data at same time ?(Firestore, Swift) - swift

I'm trying to get Document Data and Reference Data at the same time, and put data in to custom struct which consist with String Array.
When I run code below, only DocumentReference type "item1_" and "item2_" are appended.
Collection "section" has several documents. Each document has 2 Document references.
I can get right DocumentReference but I can't read it's data in same function.
How can I get both Data from Firestore?
func getall_sec(top: String, mid: String){ref.collection("top_hierarchy").document(top).collection("mid_hierarchy").document(mid).collection("section").addSnapshotListener(){ (snap, err) in
guard let docs = snap else {
self.nosecs = true
return
}
if docs.documentChanges.isEmpty{
self.nosecs = true
return
}
docs.documentChanges.forEach { (doc) in
if doc.type == .added{
let item1_ = doc.document.data()["item1"] as! DocumentReference
let item2_ = doc.document.data()["item2"] as! DocumentReference
item2_.getDocument(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.item2_name = querySnapshot?.data()?["low"] as! String
self.item2_ImageName = querySnapshot?.data()?["ImageName"] as! String
}
}
item1_.getDocument(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.item1_name = querySnapshot?.data()?["low"] as! String
self.item1_ImageName = querySnapshot?.data()?["ImageName"] as! String
}
}
self.sections.append(SectionModel(id: doc.document.documentID, item_name1: self.item1_name, item_ImageName1: self.item1_ImageName, item_name2: self.item2_name, item_ImageName2: self.item2_ImageName))
}
}
}
}

The main issue is that getDocument is asynchronous and the the self.sections.append code will actually execute before the code in the closures following the two get functions.
item2_.getDocument(){ (querySnapshot2, err2) in
//this code will execute at some point
}
item1_.getDocument(){ (querySnapshot1, err1) in
//this code will execute at some point
}
//this code will execute before the code in the two above getDocument closures.
self.sections.append(SectionModel(id: doc.document.documentID...
one solution is to nest the calls so they execute in order (this is not the best solution but demonstrates the concept)
item2_.getDocument(){ (querySnapshot, err) in
item1_.getDocument(){ (querySnapshot, err) in
self.sections.append(SectionModel(id: doc.document.documentID...
}
}

Related

How to wait for code inside addSnapshotListener to finish execution before returning to function?

func getStudents() {
var student: Student = Student()
db.collection(StudentViewModel.studentCollection).addSnapshotListener { (querySnapshot, error) in
guard error == nil else {
print("ERROR | Getting Student Documents in Firestore Service: \(String(describing: error))")
return
}
guard let snapshot = querySnapshot else {
// no documents
print("No documents to fetch!")
return
}
DispatchQueue.main.sync {
var updatedStudentDocuments: [Student] = [Student]()
for studentDocument in snapshot.documents {
student = Student(id: studentDocument.documentID,
name: studentDocument.data()["name"] as? String ?? "")
updatedStudentDocuments.append(student)
}
self.students = updatedStudentDocuments
}
}
}
Every time I run this function and check what's inside self.students, I find that it's empty. That's because the function getStudents is returning before the closure in addSnapshotListener finishes executing. How do I make the getStudents function wait for the closure to finish executing before continuing its own execution?

firestore wherefield result is <FIRQuery> in swift

my code is here and result
func documentField(){
guard let uid = Auth.auth().currentUser?.uid else {
return
}
print(uid)
let db = Firestore.firestore().collection("collection")
let data = db.orderby(by: "users").whereField("users", arraycontains: uid)
print(data)
}
console outline
MNDJR2NOx1gOcxPGJ2xOUw3PHCM2
<FIRQuery: 0x6000032555e0>
i dont know where is my fault this query result is every time comig <FIRQuery: 0x6000032555e0>
Your code creates a query, but doesn't execute it. So that means you're printing the query itself, not its results.
If you have a look at the documentation on getting documents from the database, you'll find this Swift example of how to do so:
db.collection("cities").whereField("capital", isEqualTo: true)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
You're going to want to call getDocuments() on your query in the same way and process the results you get.

How can I get the ID of an FireStore Document, if i know the value of one field? (In Swift)

I'm programming in Swift and I have a little problem.
I wondered how I can get the ID of an Document, if I have the the value for a field in it.
Little example:
document_x -> name: x, number: 5
document_y -> name: y, number: 2
document_z -> name: z, number 16
And now I know that I want the ID of the document, which has the name: z (in this case document_z)
Is this possible?
To find the document(s) matching a specific value in a field you can use a query. Something like this:
let query = collectionRef.whereField("name", isEqualTo: "z")
query.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
i had same problem some months back.fire base was generating random doc id because of not giving specific id.
there are two approaches to this:
first one : this is how i found my doc id w.r.t to string i have given in it
private func GetFireBaseID(_ ComingToBeDel : String , completion:#escaping(String) -> ()) {
db.collection(FireBaseConstant.ProjectCollectioName).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let currentdata = document.data()
if let currenttitle = currentdata[FireBaseConstant.ProjectVC.ProjectName] as? String {
if currenttitle == ComingToBeDel {
completion(document.documentID)
break
}else {
completion("")
}
}
}
}
}
}
caller is like this
GetFireBaseID(filterobj[index].ProjectTitle) { (returnid) in
if returnid.count > 0 {
db.collection(FireBaseConstant.ProjectCollectioName).document(returnid).updateData([
FireBaseConstant.ProjectVC.ProjectName : string
])
}else {
print("couldnt find your file")
}
}
second method can be using whereField filtered queries.for that please consult following link.
https://firebase.google.com/docs/firestore/query-data/queries

Firestore Querying a Snapshot within a Snapshot?

I am trying to listen for any notifications whenever someone has replied to a post that a user has commented on. Below is how my database structure looks.
Posts: (Collection)
Post 1: (Document)
replies: [user2, user3]
Replies: (Collection)
Reply 1: (Document)
ownerId: [user2]
Reply 2: (Document)
ownerId: [user3]
Currently my code has 2 snapshot listeners. The first one listens to the Posts collections, where a user is inside the 'replies' array. Then the second one listens to the Replies collection, where it returns all documents added that != the current user. When a new reply has been detected, it will set the Tab Bar item's badge.
This currently works right now, but I am curious if there is a better method of doing so.
func getNotifications() {
database.collection("Posts")
.whereField("replies", arrayContains: userData["userId"]!)
.order(by: "timestamp", descending: true)
.limit(to: 70)
.addSnapshotListener() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { documentd in
if (documentd.type == .added) {
let dataTemp = documentd.document.data()
let ifUser = dataTemp["ownerId"] as! String
if(ifUser == self.userData["userId"]!) {
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("timestamp", isGreaterThan: dataTemp["timestamp"] as! Int)
.addSnapshotListener() { (querySnapshot3, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot3 else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let temp = diff.document.data()
if((temp["ownerId"] as! String) != self.userData["userId"]!) {
print("new reply")
newArr.append(diff.document.data())
let data = diff.document.data()
let firebaseTime = data["timestamp"] as! Int
let date = lround(Date().timeIntervalSince1970)
if(firebaseTime+10 > date) {
self.tabBar.items![2].badgeValue = "●"
self.tabBar.items![2].badgeColor = .clear
self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
}
}
}
}
}
}
}
else {
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("ownerId", isEqualTo: self.userData["userId"]!)
.order(by: "timestamp", descending: false)
.limit(to: 1)
.getDocuments() { (querySnapshot2, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
var timestamp = Int()
for documentde in querySnapshot2!.documents {
let temp = documentde.data()
timestamp = temp["timestamp"] as! Int
database.collection("Posts")
.document(documentd.document.documentID)
.collection("Replies")
.whereField("timestamp", isGreaterThan: timestamp)
.addSnapshotListener() { (querySnapshot3, err) in
if let err = err {
print("Error getting documents: \(err)")
}
else {
guard let snapshot = querySnapshot3 else {
print("Error fetching snapshots: \(err!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let temp = diff.document.data()
if((temp["ownerId"] as! String) != self.userData["userId"]!) {
print("new reply")
newArr.append(diff.document.data())
let data = diff.document.data()
let firebaseTime = data["timestamp"] as! Int
let date = lround(Date().timeIntervalSince1970)
if(firebaseTime+10 > date) {
self.tabBar.items![2].badgeValue = "●"
self.tabBar.items![2].badgeColor = .clear
self.tabBar.items![2].setBadgeTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for:.normal)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
Your code does not have only two listeners, but one listener per post for which the user you are interested in has ever replied for. This will lead to terrible performances very soon and will potentially crash you app as Firestore has a limitation of 100 listeners per client.
I would advise to redesign your data model:
Only one listener on posts that the user has ever replied to (your first listener)
On each reply increment a reply counter in the post doc, this will trigger the snapshot above.
Optimisation 1: on each action on a post you could set a field lastactiontype, which would have a specific value reply for replies only. This way the snapshot is only triggered on replies.
Optimisation 2: set a field timestamp on each action to the current time and only pull the last n (for instance 10) posts in your snapshots, this will limit the number of reads at load time. You will have to implement some special logic to handle the case when your app goes offline and back online and all n posts of the snapshot have changed. This is a must if your app is meant to scale (you dont want a snapshot with no limit on a collection with 100k docs...)
Example:
firestore.collection("Posts")
.where( "lastaction", "==" , "reply")
.where( "replies", "array-contains", uid)
.orderBy("timestamp", "desc").limit(10)

Why does my firestore collection return 0 documents

Here is my database.
This is my code
let db = Firestore.firestore()
let defaults = UserDefaults.standard
let userId: String! = defaults.string(forKey: "UserUUID")
db.collection("Portfolios")
.whereField("users." + userId, isEqualTo: true)
.limit(to: 1)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for portfolioDocument in querySnapshot!.documents {
print("Portfolio: \(portfolioDocument.documentID) => \(portfolioDocument.data())")
let portRef = db.collection("Portfolios").document("portfolioDocument.documentID")
portRef.getDocument { (document, error) in
if let document = document {
print("Document data: \(document.data())")
portRef.collection("Accounts").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
var docs:[DocumentSnapshot] = []
for document in querySnapshot!.documents {
I have even harde coded the "Mogu..." Document ID to no effect. What is the obvious thing I am missing?
querySnapshot.documents is 0 at the last line:
for document in querySnapshot!.documents {
EDIT: It is the last query that returns 0 documents. The rest work:
no, that has no effect on the top level as that is successful. It is the last query that is brings 0 documents: portRef.collection("Accounts").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
var docs:[DocumentSnapshot] = []
for document in querySnapshot!.documents {
The Coinbase and Default documents in your Accounts collection don't actually exist, see non-existent docs in the console. In your screenshot, notice that the document names for Coinbase and Default are italicized and grayed out. This indicates that these documents weren't directly created but that they are on the path of a document nested deeper down which does exist.
Try setting a field value within Coinbase and Default. This should get them to appear in your query.