0

I have a list of users in an array: let userArray = ["userId1", "userId2"]. I'm trying to pull the list of all comments for a given postId and filter the results to only pull those where the userId is in userArray. Below is my code so far but it doesn't seem to be working. Is there a better way to approach the problem?

//pulls all comments given a postId of "postId1"
  let commentsRef = databaseRef.child("comments").queryOrdered(byChild: "postId").queryEqual(toValue: "postId1")

      commentsRef.observe(.value, with: { (comments) in

        var resultArray = [Comment]()
        for comment in comments.children {

           let value = child.value as? NSDictionary

           let userId = value?["userId"] as? String ?? ""

               for contactinfo in userArray {
                   if(contactinfo = userId) {
                     let comment = Comment(snapshot: comment as! DataSnapshot)
                                resultArray.append(comment)
                            }
                        }
                        completion(resultArray)

                    }
                })
    })

Firebase:

Comments
-CommentId1
 -postId: "postId1"
 -userId: "UserId1" //RETRIEVE
-CommentId2
 -postId: "postId2"
 -userId: "UserId50" //DO NOT RETRIEVE
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
John Wong
  • 25
  • 8
  • "I have a list of users in an array" Firebase also has a list of products. – El Tomato Feb 24 '18 at 04:48
  • Firebase Database queries can only order/filter on a single property. In many cases it is possible to combine the values you want to filter on into a single (synthetic) property. For an example of this and other approaches, see my answer here: http://stackoverflow.com/questions/26700924/query-based-on-multiple-where-clauses-in-firebase – Frank van Puffelen Feb 24 '18 at 05:19
  • I'm going with @FrankvanPuffelen on this. As an alternative, depending the quantity of data, you could add a .childAdded event to the Comments node - that will iterate over each child node (CommentId1, CommentId2 etc). Compare the userId child value with what's in your array and ignore the ones that don't match and process the ones that do. OR if there are just a couple hundred comments, read them in via observerSingleEvent(.value... and iterate over the child nodes in code; ignore the ones that don't match and process the ones that do. It all depends on how many child nodes and qty of data. – Jay Feb 24 '18 at 14:01
  • As a side note, your code isn't working because the database node is Comments (capital 'C') and your code is trying to read 'comments' (lower case 'c'). Other than that, it's pretty close to one of my suggestions, although I would suggest creating a class var array to store the comment (self.commentArray) and populating that and upon completing the for loop do something with the array. Also, you are adding an observer query so anytime anything changes that matches your query, the code in the closure will be called - not sure if that was intentional or not. – Jay Feb 24 '18 at 14:03
  • Thanks Jay. Sorry, I'm relatively new to Firebase so excuse my basic questions. Can you provide an example of what you mean by this "As an alternative, depending the quantity of data, you could add a .childAdded event to the Comments node - that will iterate over each child node (CommentId1, CommentId2 etc). Compare the userId child value with what's in your array and ignore the ones that don't match and process the ones that do." What would a sample code look like – John Wong Feb 25 '18 at 23:46

2 Answers2

0

In this condition, you are doing a 'and' query, according to the doc, you should put multiple query together like this.

// Create a reference to the cities collection
let commentsRef = db.collection("comments")

// Create a query against the collection.
commentsRef.whereField("postID", isEqualTo: "post1")
commentsRef.whereField("userID", isEqualTo: ['user1', 'user2'])

Checkout the doc for more detail. https://firebase.google.com/docs/firestore/query-data/queries.

---EDIT---

Emmmmm, I will remain above answer if you change your database selection.

I read this link Query based on multiple where clauses in Firebase .

So I think you should be able to add a custom index on you comments model like this

Comments
  -CommentId1
    -postId: "postId1"
    -userId: "UserId1" //RETRIEVE
    -idx: "postID1+UserID1"

Now you could do the query on this index. And if you want to retrieve multiple users records, you could simply loop the users array.

crazy_phage
  • 550
  • 9
  • 28
  • I should have mentioned that I'm using the realtime database so it doesn't have that function. https://firebase.google.com/docs/database/ios/read-and-write – John Wong Feb 24 '18 at 05:11
  • The API you call is for Cloud Firestore. OP is using the Firebase Realtime Database. – Frank van Puffelen Feb 24 '18 at 05:18
  • Emmmm, in this condition, I think you should write a cloud function to extend what your need. – crazy_phage Feb 24 '18 at 05:19
  • Can you please update your answer to make it an answer? As is, it's not really helpful. Perhaps showing what you mean and an example of a *Cloud Function* and a trigger that would provide the result the OP wanted? – Jay Feb 24 '18 at 14:10
  • But keep in mind: Cloud Functions is just a (admittedly very convenient) place to run code that triggers on and interacts with Firebase. It can't magically do something that the API doesn't allow. Since the Firebase Realtime Database doesn't allow filtering on multiple properties, that also isn't possible in Cloud Functions. – Frank van Puffelen Feb 24 '18 at 15:17
0

Here are two examples of how to retrieve the posts from a specific user - either option could be expanded to exclude posts from unwanted users by adding additional if statements.

Here's the Firebase structure

comments
-CommentId1
 -postId: "postId1"
 -userId: "UserId1" //RETRIEVE
-CommentId2
 -postId: "postId2"
 -userId: "UserId50" //DO NOT RETRIEVE

and the code tied to two different buttons in the UI

var postsArray = [String]()
let userIdWeWant = "userId1"

func button0() {
    self.postsArray = []
    let commentsRef = self.ref.child("comments")
    commentsRef.observe(.childAdded, with: { snapshot in
        let dict = snapshot.value as! [String: Any]
        let userId = dict["userId"] as! String
        if userId == self.userIdWeWant {
            let post = dict["postId"] as! String
            self.postsArray.append(post)
            print(post)
        }
    })
}

func button1() {
    self.postsArray = []
    let commentsRef = self.ref.child("comments")
    commentsRef.observeSingleEvent(of: .value, with: { snapshot in
        for child in snapshot.children {
            let childSnap = child as! DataSnapshot
            let dict = childSnap.value as! [String: Any]
            let userId = dict["userId"] as! String
            if userId == self.userIdWeWant {
                let post = dict["postId"] as! String
                self.postsArray.append(post)
            }
        }
        print(self.postsArray)
    })
}

As it is, we are just capturing posts from userId1 and adding them to an array, but say we wanted to capture posts from any user that loves pizza and has a shoe size of 13. So the structure would be

comments
    -CommentId1
     -postId: "postId1"
     -userId: "UserId1" //RETRIEVE
     -food: "Pizza"
     -shoe: "13"

and a code snippet would be

let shoe = dict["shoe"] as! String
let food = dict["food"] as! String
if shoe == self.shoeWeWant && food == self.foodWeWant {

EDIT

Per a question in a comment, 'get any uid stored in the uid array', it's super simple in Swift 4. Here's an example to see if an object exists in an array

var uidArray = ["uid0", "uid1", "uid2", "uid3"]
if uidArray.contains("uid1") {
    print("it exists! Do something with it!")
} //otherwise ignore it

that technique could be used to see if the read in array with

let userId = dict["userId"] as! String

exists in the array of users you are interested in.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • This is great. But what if the userIdWeWant is any userId stored in the array userIdArray. How would we modify the code? – John Wong Mar 01 '18 at 01:20
  • @JohnWong Great question with a super simple answer. I edited my question with an additional answer to address your question. – Jay Mar 01 '18 at 15:54