Exclude certain child from retrieving Firebase - swift

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.

Related

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)
}
}
}

Displaying all posts using Firebase in Swift?

My goal is so that when users open the app they will see every post from every user of the app. My data tree is as follows:
- posts
- UID
- postKey
--attributes
- users
- UID
- attributes
Here's an example:
-posts
-74anqEXU8kQHVr7IKoO3N9NNqDh1
-MLzvs5VvXB_z7fhbh2p
created_at: 1605242945.969368
image_height: 414.6865671641791
image_url: "https://firebasestorage...."
-MLzvun01fNXNRbn7TPv
-MM7zGyZ7GbenhlisJUZ
-VGxqdzc2CkWWn39pa8xUofEWgNm2
-pNvGg84JR0TbXvS4XlX8KbfBasz2
-users
-74anqEXU8kQHVr7IKoO3N9NNqDh1
-VGxqdzc2CkWWn39pa8xUofEWgNm2
I know I have to make a snapshot of all the posts, and then display it in viewDidLoad() of the main controller (SearchViewController). I can't figure out how to display all posts.
This code works for displaying the Current User's posts, stored in a file called UserService.swift:
static func posts(for user: User, completion: #escaping ([Post]) -> Void) {
let ref = Database.database().reference().child("posts").child(user.uid)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshot = snapshot.children.allObjects as? [DataSnapshot] else {
return completion([])
}
let dispatchGroup = DispatchGroup()
let posts: [Post] = snapshot.reversed().compactMap {
guard let post = Post(snapshot: $0) else { return nil }
dispatchGroup.enter()
SaveService.isPostSaved(post) { (isSaved) in
post.isSaved = isSaved
dispatchGroup.leave()
}
return post
}
dispatchGroup.notify(queue: .main, execute: {
completion(posts)
})
})
}
And then in ViewDidLoad() of SearchViewController:
UserService.posts(for: User.current) { (posts) in
self.posts = posts
self.tableView.reloadData()
}
But how do I do this for all posts. When I try the following, it won't let me specify a random user:
let ref = Database.database().reference().child("posts").child(User)
Any idea what I can put in the ".child(User" box to make this work? Or how to create a for-loop to properly iterate through?
*EDIT
Here is what worked:
static func allPosts(completion: #escaping ([Post]) -> Void) {
let ref = Database.database().reference().child("posts")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
var postsArray = [Post]()
for userSnapshot in snapshot.children {
guard let snapshot = (userSnapshot as AnyObject).children.allObjects as? [DataSnapshot] else {
return completion([])
}
let dispatchGroup = DispatchGroup()
let posts: [Post] = snapshot.reversed().compactMap {
guard let post = Post(snapshot: $0) else { return nil }
postsArray
postsArray.append(post)
dispatchGroup.enter()
SaveService.isPostSaved(post) { (isSaved) in
post.isSaved = isSaved
dispatchGroup.leave()
}
return post
}
dispatchGroup.notify(queue: .main, execute: {
completion(postsArray)
})
}
})
}
I may be overlooking something but it appears the goal is to
when users open the app they will see every post from every user
If that's the case, sometimes simpler is better; load all of the posts, iterate over them creating your Post object for each then reload the tableview (?)
let postsRef = self.ref.child("posts") //self.ref points to my firebase
postsRef.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
let aPost = Post(initWithSnapshot: postSnap)
self.postsArrray.append(post) //
}
self.postTableView.reloadData()
}
This assumes your Post class has an convenience init to populate its properties from the snapshot. If so, set isSaved to true within that since the convenience init is being called with saved data.
If you're calling this when the app starts or view loads, there's no need for callbacks and dispatchQueues.
You can observe one level higher in the JSON, and then add an extra loop in the callback:
let ref = Database.database().reference().child("posts")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
for userSnapshot in snapshot.children {
guard let snapshot = userSnapshot.children.allObjects as? [DataSnapshot] else {
return completion([])
}
let dispatchGroup = DispatchGroup()
let posts: [Post] = snapshot.reversed().compactMap {
guard let post = Post(snapshot: $0) else { return nil }
dispatchGroup.enter()
SaveService.isPostSaved(post) { (isSaved) in
post.isSaved = isSaved
dispatchGroup.leave()
}
return post
}
}
dispatchGroup.notify(queue: .main, execute: {
completion(posts)
})
})

Problem while retrieving info from Firebase Database IOS

I'm trying to retrieve data from my Database but I'm having some problems, this is my database structure:
And here is my code
var ref : DatabaseReference
var idString = [String]()
ref = Database.database().reference()
ref.child("idUsers").observe(.value){ (snapshot) in
let id = snapshot.value as? String
if let ids = id {
idString.append(ids)
print(ids)
}
}
But no data is going into my array, I have been trying some solutions but no one works, please help! These are my rules by the way, they are public by default.
{
"rules": {
".read": true,
".write": true
}
}
Since idUsers contains multiple child nodes, you need to loop over the results in your code. Something like this:
ref.child("idUsers").observe(.value){ (snapshot) in
for userSnapshot in in snapshot.children.allObjects as? [DataSnapshot] {
let id = userSnapshot.value as? String
print(id)
idString.append(id)
}
print(ids)
}
**For creating database structure**
`override func viewDidLoad() {
var ref: DatabaseReference!
ref = Database.database().reference()
Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
Auth.auth().signIn(withEmail: email, password: password) { [weak self] user, error in
guard self != nil else { return }
ref.child("users").setValue(["test1": "abc",
"test2": "efg",
"test3": "hij"])
}
}
}`
**// For retrieving data from firebase**
ref.observe(DataEventType.value, with: { (snapshot) in
let postDict = snapshot.value as? [String : AnyObject] ?? [:]
for item in postDict {
print(item)
}
})

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

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)")
}

Firestore returning out of function and not appending any objects to array

For some reason when i add the code if user.uid == uid {return}
it will return out of the function altogether and not add any users to the array even though i only want to filter out the current user from being added to the array?
func fireStoreFetchUsers(){
guard let uid = Auth.auth().currentUser?.uid else { return }
let db = Firestore.firestore()
db.collection("Users")
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let dictionary = document.data()
let user = User2(uid: "", distanceFrom: "any", dictionary: dictionary)
if user.uid == uid {return}
self.users.append(user)
self.collectionView?.reloadData()
}
}
}
}
It seems that your problem in summary is that you want to skip a specific element of the array you are iterating through. This is exactly what continue does, so just change return to continue.
After the above change, the last two lines of the loop won't be executed when user.uid == uid and hence that specific user won't be added to the users array and the collectionView won't be reloaded, but the loop will continue its iteration with the next element rather than returning from the function.
for document in querySnapshot!.documents {
let dictionary = document.data()
let user = User2(uid: "", distanceFrom: "any", dictionary: dictionary)
if user.uid == uid {continue}
self.users.append(user)
self.collectionView?.reloadData()
}