Unable to fetch data from existing document within subcollection (Swift & Firestore) - swift

Messaging Structure:
messages > currentUserID (document) > partnerID (collection) > message (document)
I can get as far as retrieving the partner ID but I can't retrieve the individual messages (documents) within the collection. Heres the functions Im using:
func observeUserMessages(){
guard let uid = Auth.auth().currentUser?.uid else { return }
let dbRef = db.collection("messages").document(uid).addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot?.documentID else { return }
print("This is the partner ID: \(snapshot)")
self.fetchMessageWithPartnerID(partnerID: snapshot)
}
self.tableView.reloadData()
}
fileprivate func fetchMessageWithPartnerID(partnerID: String) {
guard let uid = Auth.auth().currentUser?.uid else { return }
Firestore.firestore().collection("messages").document(uid).collection(partnerID).getDocuments { (snapshot, err) in
print("This is the snapchat count:\(snapshot?.count)")
}
}
Results:
As you can see, it should show the two messages but its not returning anything.

I think there's a difference between .collection() and .document(). Try
Firestore.firestore().collection("messages").collection(uid).collection(partnerID).getDocuments { (snapshot, err) in
print("This is the snapchat count:\(snapshot?.count)")
}

Related

How can i retrieve data from a collection if the condition is set firestore swift

Here is my code pls help
My target is to retrieve a list of channels that contain uid within Subscriptions collection.
func getUserSubscriptions(uid: String) {
db.collection("Channels").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
//No documents found
return
}
for document in querySnapshot!.documents {
document.reference.collection("Subscribers").whereField("user_id", isEqualTo: uid).addSnapshotListener { (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
//No documents found
return
}
//I want to get Channels that only contain uid withing it's Subscribers collection, but unfortunately it only gets the whole Channels, please help.
DispatchQueue.main.async {
self.channels = documents.map { d in
return Channels(id: d.documentID, channel_id: d["channel_id"] as! String?, channel_name: d["channel_name"] as? String ?? "", creator: d["creator"] as? String ?? "")
}
}
}
}
}
}
Pls help I got stuck here.

firestore fetch subcollection

I'm trying to fetch subcollection of my users document with code below
func readData(){
let userId = Auth.auth().currentUser?.uid
self.db.collection("users/\(userId)/saved").getDocuments { (snapshot, err) in
if let err = err {
print("err")
}
if let userId != nil {
for document in snapshot!.documents {
let docId = document.documentID
let cty = document.get("city") as! String
let ccode = document.get("code") as! String
let countr = document.get("country") as! String
print(cty, ccode, countr,docId)
}
}
}
but my code doesn't print anything, I don't understand the problem, documents exsist, see picture below
You're using illegal syntax with the userId check in the snapshot return but the logic flow is the bigger problem. I would recommend you check if the user is signed in before grabbing the subcollection and checking if there is a viable snapshot instead of checking the state of authentication.
func readData() {
guard let userId = Auth.auth().currentUser?.uid else {
return
}
db.collection("users/\(userId)/saved").getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
return
}
for doc in snapshot.documents {
guard let city = doc.get("city") as? String,
let code = doc.get("code") as? String,
let country = doc.get("country") as? String else {
continue // continue document loop
}
let docId = doc.documentID
print(city, code, country, docId)
}
}
}

Exclude certain child from retrieving Firebase

I would like to retrieve all children within a certain collection except one specific child. I have the database "users" which consists of multiple user id's.
"users"
|- "userId1"
|- "userId2"
|- "userId3"
and so on.
I now want to retrieve only "userId1" and "userId3" and exclude "userId2". I already know, how to get just the userId2, because I have also stored it in another database, "blocked-users". This database consists of userIds as well as is build like this:
"blocked-users"
|- "userId1"
|- "userId2"
And that is the reason why I want to exclude userId2 from retrieving the users. Say userId1 is the currentUser and has blocked userId2, I want to prevent userId1 from finding userId2. How can I do this?
This is how I am getting the id of userId2:
guard let currentUid = Auth.auth().currentUser?.uid else { return }
BLOCKED_USERS.child(currentUid).observe(.childAdded) { (snapshot) in
print("this is the user, that should not be shown: ", snapshot.key)
How can I now exclude the snapshot.key from being fetched within this function?:
var userCurrentKey: String?
USER_REF.queryLimited(toLast: 10).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let uid = snapshot.key
Database.fetchUser(with: uid, completion: { (user) in
self.users.append(user)
self.tableView.reloadData()
})
})
self.userCurrentKey = first.key
}
This right here would be the entire function I call for fetching all the users:
func fetchUsers() {
guard let currentUid = Auth.auth().currentUser?.uid else { return }
if userCurrentKey == nil {
BLOCKED_USERS.child(currentUid).observe(.childAdded) { (snapshot) in
print("these are the users that have blocked this user: ", snapshot.key)
var dontShowThisUser = snapshot.key
USER_REF.queryLimited(toLast: 10).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let uid = snapshot.key
Database.fetchUser(with: uid, completion: { (user) in
self.users.append(user)
self.tableView.reloadData()
})
})
self.userCurrentKey = first.key
}
}
} else {
USER_REF.queryOrderedByKey().queryEnding(atValue: userCurrentKey).queryLimited(toLast: 5).observeSingleEvent(of: .value, with: { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.removeAll(where: { $0.key == self.userCurrentKey })
allObjects.forEach({ (snapshot) in
let uid = snapshot.key
if uid != self.userCurrentKey {
Database.fetchUser(with: uid, completion: { (user) in
self.users.append(user)
if self.users.count == allObjects.count {
self.tableView.reloadData()
}
})
}
})
self.userCurrentKey = first.key
})
}
}
I have found a way. In the Firebase rules I solve this problem like this:
"$users": {
"$uid": {
".read": "auth != null && !(root.child('blocked-users').child(auth.uid).hasChild($uid))",
".write": "auth != null"
}
}
This reads like this: When auth is not null and the user is not in the blocked-users collection of the user they want to find, the user is allowed to read the user's data.
In general there is no way to exclude one (or some) nodes from a query. You will either have to load the entire users node and filter out the users you don't want in your application code, or you'll have to load the users you do want one by one.
The only variation on this is if you can use a query to slice the child nodes that you want and don't want. For example, if you have 99 users (user01 to user99 for simplicity) and won't read all but one user (say user42), you could do that with two queries:
usersRef.queryOrderedByKey().queryEnding(beforeValue: "user42")
and
usersRef.queryOrderedByKey().queryStarting(afterValue: "user42")
I don't think there's a gain by using this approach here though, as this is likely to be (slightly) less efficient and be more code to handle the two queries, than it'd be to filter the one node in the application code.

swift wait until array is set before calling function

I would like to call fetchArrayOfUsersBlockingTheCurrentUser() and wait until the array of blocked users is complete before calling fireStoreFetchUsers(), as I would like to dismiss blocked users from the usersarray before loading the users on the screen.
so far the screen loads all users before the array is set and only work when leaving the screen and coming back to the screen
I have tried did set but it calls fetchusers to many times putting the same user multiple times on the screen.
code below:
func fetchArrayOfUsersBlockingTheCurrentUser() {
guard let uid = Auth.auth().currentUser?.uid else { return }
let db = Firestore.firestore()
let docRef = db.collection("Users").document(uid).collection("Users Blocking Me")
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let d = document.documentID
self.UsersBlockingCurrentUserArray.append(d)
}
}
}
fireStoreFetchUsers()
}
var UsersBlockingCurrentUserArray = [String]()
var users = [User2]()
and the function fireStoreFetchUsers() basically goes;
///fetche users from database
///users.append(user)
//reload data
Don't wait. Move fireStoreFetchUsers() into the completion block.
func fetchArrayOfUsersBlockingTheCurrentUser() {
guard let uid = Auth.auth().currentUser?.uid else { return }
let db = Firestore.firestore()
let docRef = db.collection("Users").document(uid).collection("Users Blocking Me")
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let d = document.documentID
self.UsersBlockingCurrentUserArray.append(d)
}
self.fireStoreFetchUsers()
}
}
}
And please conform to the naming convention that variable names start with a lowercase letter

What is the proper way to return a firestore documentID in swift?

I would like to create a function that will return a documentID given a value of a field. I am getting tired of my nested functions. Is there a simple way to have one function that returns the ID ?
Here is what I have been doing. It is an ugly pattern.
public class func getAccountItems(accountName:String) {
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 {
var documentID:String? = nil
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
documentID = document.documentID
if(documentID != nil){
do {
let db = Firestore.firestore()
let defaults = UserDefaults.standard
let portfolioId: String! = defaults.string(forKey: listDocKey)
db.collection("Portfolios").document(portfolioId).collection("Accounts").whereField("name", isEqualTo: accountName)
.getDocuments(){ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if querySnapshot!.documents.count == 1 {
for document in querySnapshot!.documents {
db.collection("Portfolios").document(documentID!).collection("Accounts").document(document.documentID).collection("Items")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
var items:[INDX01FirestoreService.PortfolioItem] = []
for document in querySnapshot!.documents {
print("\(accountName): \(document.documentID) => \(document.data())")
let pi = try! FirestoreDecoder().decode(INDX01FirestoreService.PortfolioItem.self, from: document.data())
items.append(pi )
}
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "AccountItemsReceived"), object: nil, userInfo: ["items": items])
}
}
}
}else{
print ("Error count is \(querySnapshot!.documents.count)")
}
}
}
}
}
}
}
}
}
Since Firestore is an async call I either do it this way or I send a notification. I don't like sending notifications all over the place.
Cant post a comment but one thing why do you have so much db references
let db = Firestore.firestore()
this should give you id of the document like you have
documentID = document.documentID
Post here how your data structure looks like and what ids you want to get also have in mind you should store id in each document.
I try to help you.
I decided to just store the documentID in UserDefaults as that handles most of the use cases. Otherwise I do the above, search, find, then process