Search code examples
swiftfirebasefirebase-realtime-databasesnapshot

Instead of running a snapshot for all users, how do you set up multiple queries to limit the number of users sent to the device?


What I have: A snapshot of all users with a bunch of if statements that eventually returns an array of users that get displayed.

What I need: The array of end users to be used in a .query in the line preceding the snapshot.

Why do I need this: This line is so that the entire database of users is not run on the client.

More specifically, what do I need to query for: A) Users who have a child "caption"(timestamp) with a timestamp that is in today, AND, B) who are 3000 miles from the current user.

JSON of DB

    "people" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
  "Coordinates" : {
    "latitude" : -25.809620667034363,
    "longitude" : 28.321706241781342
  },
  "PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2F02PdiNpmW3MMyJt3qPuRyTpHLaw2%2FPhotoPosts?alt=media&token=24fee778-bcda-44e3-aa26-d7c2f8509740",
  "caption" : 1602596281762, /// this is timestamp
  "postID" : "02PdiNpmW3MMyJt3qPuRyTpHLaw2"
},
  "e1" : “cvvvv666",
  "e2" : "aol.com",
  "      "postID" : "0RnqWV7Gd9Z0bUW9nUvizMQOjK73",
  "users" : "[email protected]"
},
    

.

var dict = CLLocation()
         ...
dict = CLLocation(latitude: lat, longitude: lon)
        ...
let thisUsersUid = Auth.auth().currentUser?.uid
    //this line below is where the refArtists2 query should go. in other words send all users to device that meet the 2 if statements, which is represented by self.people.append(peopl)//
    let refArtists2 = Database.database().reference().child("people").queryOrdered(byChild: "caption").queryEqual(toValue: ANY Timestamp in today).queryOrdered(byChild:Coordinates). queryEqual(toValue:ThoseCoordinates which make the distance to current user less than 3000 miles)     
    refArtists2.observe(DataEventType.value,  with: {  snapshot in
        if snapshot.childrenCount>0{
            self.people.removeAll()
            for people in snapshot.children.allObjects as! [DataSnapshot] {
         if people.key != thisUsersUid {
                let peopleObject = people.value as? [String: AnyObject]
                let peopleCoordinates = peopleObject?["Coordinates"] as? String
                let peoplecaption = peopleObject?["caption"] as? Int //is timestamp
                let peoplepostID = peopleObject?["postID"] as? String
                let coordSnap = people.childSnapshot(forPath: "Coordinates")
                guard let lat = coordSnap.childSnapshot(forPath: "latitude").value as? CLLocationDegrees else { return  }
                guard let lon = coordSnap.childSnapshot(forPath: "longitude").value as? CLLocationDegrees else { return  }
                let locCoord = CLLocation(latitude: lat, longitude: lon)
                let coordSnap12 = people.childSnapshot(forPath: "caption").value as? Int ?? 0
                let date = Date(timeIntervalSince1970: TimeInterval(coordSnap12)/1000.0)
                //let secondsInDay = 86400
                **if Calendar.current.isDateInToday(date)** {
                    let distance = locCoord.distance(from: self.dict)
                    print(distance, "distancexy")
                    **if distance/1609.344 < 3000**{
                        let peopl = Userx(Coordinates: peopleCoordinates, distance:distance, caption: peoplecaption, postID: peoplepostID)
                        self.people.append(peopl)
                        let d = people.key as! String
                        self.printPersonInfo(uid:d)   ///////This is used to reload the data
                    } else {
                        print ("w")
                    }
                } else {
                    print ("alphaaa") 
                }   
            }
            print("aaaaaaaa", self.people.map {$0.distance})    
        } 
        self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }     ////////This sorting with distance is used with returning the cell. people is used as uid array to return the cell.   
    }      
})
} else {
    print("no")
}
})
                            

Ancillary caveat: the self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }sorting is important, so the queries should not impede that. I am a bit concerned with using queryOrdered in that it orders the array of users in the wrong order. If it does, a C) query should be: The order of the users must be with the closest users to the logged in user first. The furthest from the logged in user must go last in the array.

Another way of asking this would be: Instead of running a snapshot of all users, how do you query the snapshot's 'end result sort' when making the snapshot?

The timestamp is seconds since 1970

My attempt at the date query below. I took the code and tried to put the code that gets the date before the actual query(currently the code that gets the date is after the snapshot of all users).

    var ppp: String!  ////this should be the uid of all users in db

    let people = Database.database().reference().child("people").child(self.ppp).child("captions")
people.observe(DataEventType.value, with: {  snapshot in
    let captionss = snapshot.value as? Int ?? 0
    let date = Date(timeIntervalSince1970: TimeInterval(captionss)/1000.0)       
    let query1 = Database.database().reference().child("people").queryOrdered(byChild: "caption").where?(isDateInToday(date))

Solution

  • Edit: This answer is in Firestore, not Realtime Database. However, the concepts are the same.

    The question is several questions in one; asking about distance, compound queries and how to query Firebase in general. I will post this answer to address the second two and distance queries are addressed in the comment to the question.

    Once the query pattern is understood, they become easier and most importantly; it becomes more obvious that how your data is structured depends on what queries you want to run against that data.

    Suppose we have a users collection with user documents - each documentId is the users uid

    users
       uid_0
          name: "Leroy"
    

    and then we have the posts for the users - each post contains some text, a timestamp of the post, the uid of the user that posted it, what the topic is and a url of a picture that appears in the post. Notice I am storing posts in a separate collection; why read in a bunch of user data when we only want to know about their post.

    posts
       post_id
          postText: "pretty flowers"
          postDate: "20201103"
          postUrl: "www....."
          postUid: "uid_0"
          postTopic: "flowers"
    

    Let suppose we want to get posts from today that are about flowers, and then also get the posters name and output who posted the message and what they said.

    To do this we will need a compound query and then a subquery to retrieve the posters name as well.

    func getTodaysPostsAboutFlowers() {
        let postsCollection = self.db.collection("posts")
        let query = postsCollection.whereField("postDate", isEqualTo: "20201103").whereField("postTopic", isEqualTo: "flowers")
        query.getDocuments(completion: { snapshot, error in
            if let err = error {
                 print(err.localizedDescription)
                 return
             }
    
             guard let docs = snapshot?.documents else { return }
    
             for doc in docs {
                let postText = doc.get("postText") as? String ?? "No text"
                guard let postersUid = doc.get("postUid") as? String else { return }
                self.outputPostTextAndUserName(withText: postText, andUid: postersUid)
             }
        })
    }
    

    The above performs a compound query on both the postDate field as the postTopic field.

    The above then calls another function to retrieve the users name and output both the name and what they said

    func outputPostTextAndUserName(withText: String, andUid: String) {
        let usersCollection = self.db.collection("users")
        let theUserDoc = usersCollection.document(andUid)
        theUserDoc.getDocument(completion: { documentSnapshot, error in
            if let err = error {
                 print(err.localizedDescription)
                 return
             }
    
            if let doc = documentSnapshot {
                let postersName = doc.get("name") as? String ?? "No Name"
                print("\(postersName) posted: \(withText)")
            }
        })
    }
    

    and the output

    Leroy posted: pretty flowers
    

    As you can see, there's no need to load all of the users, no need to iterate over results etc. Even if you have a billion users, this will only return a subset of that data which is a best practice when working with huge data sets; only get the data you're interested in.

    Edit. The OP is asking about querying for nodes containing today. The simple solution is to have one child node containing a timestamp which would contains specific date data and then another child node just containing today data in YYYYMMDD format.

    people uid_x timetamps: 9023490823498 //Date(timeIntervalSince1970: todaystamp: "20201106" // yyyymmdd format

    that makes querying for nodes that contain today very simple.