Firebase/Swift queryOrder then match value - swift

I asked a question yesterday that was marked as a duplicate, and when I updated the question it was not unmarked. So I am asking again here (as per stackoverflow's recommendation).
I am trying to sort by multiple values in firebase. I understand that is not possible, but i was given an example in another language which is only half helpful as how to go about doing it the right way. In any case i tried to follow the example given here Query based on multiple where clauses in firebase .
This is the structure of my firebase
room
-KJe22sduQMz1DIs_DH6
allowedParticipants:
14
createdBy:
"Mr Tester"
members:
"nmeMYnnSatRch5qKPJKIe7jEOLy2"
participating:
true
status:
"seedling"
theme:
"Cats"
totalNumberOfMembers:
1
and this is the code that I am trying to get to work
ref.queryOrderedByChild("status").queryStartingAtValue("active").queryEndingAtValue("active").observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
let themeOfEvent = snapshot.value
if themeOfEvent?.value == pickedTheme {
print("foo")
}
}
Could somebody please post a useful comment or answer to help me?
Thank you

I was able to get help
This works
func listOfPossibleCompetitionsFromFirebase(){
let createdRoomRef = firebase.child("room")
createdRoomRef.queryOrderedByChild("status").queryStartingAtValue("active").queryEndingAtValue("active").observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
var themeCount = 0
self.listOfOpenComps.removeAll()
if let tmp = snapshot.value as? [String:AnyObject] {
let keys = tmp.keys
for key in keys {
if let roomDetails = (tmp[key] as? [String:AnyObject]) {
if let themeOfEvent = roomDetails["theme"] as? String where themeOfEvent == pickedTheme {
themeCount += 1
self.listOfOpenComps.append(key)
}
}
}
}
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
print("rooms count: \(themeCount)")
}
}

Related

How to get an array from Firestore document on Swift 5?

Please help me understand the logic reading data from the Firestore document, if one of the values is an array. I tried other answers here and sources but never came to a simple working way and understand clearly. Firestore document structure — example. And Swift class targets for saving (conditional):
struct MyStruct {
var name: String
var pages: Int
}
let part1 = [MyStruct]()
let name1 = ""
let pages1 = 0
let part2 = [MyStruct]()
let name2 = ""
let pages2 = 0
func readFirestore() { }
What should the document reader function look like to add data to existing targets in the class? Thanks in advance for any help in improving my understanding!
They helped to deal with familiar, thank you for what they are. As expected, everything is simple. But for the beginner there is nothing more difficult than simplicity 😁
func readFirestore() {
self.db.collection("example").document("book").getDocument { (document, error) in
if error == nil {
if document != nil && document!.exists {
//get all document data
guard let documentData = document!.data() else {return}
//get value-array for key "part1"
let element = documentData["part1"] as? [Any] //print -> Optional([name1, 100])
//get first element in array
guard let nameDB = element?[0] as? String else {return} //print -> name1
guard let pagesDB = element?[1] as? String else {return} //print -> 100
//append in class
part1.append(MyStruct(name: nameDB, pages: pagesDB))
name1 = nameDB
pages1 = pagesDB
}
}
}
}

Swift Firebase Firestore userlist (too many reads)

I'm trying to find a solution for a little problem I have with my app. It's a chess app and it works with Firebase and is written in swift. I use the database for user authentication, user information and uploading moves to play against each other online. All the userInfo is saved in a document in the collection "allUsers".
Everything is working fine, but I have a user screen where you can press a refresh button to update the current online users, with the .getDocuments() function. The problem is that every time a user refreshes, they query through all of the registered accounts, check if they have the flag "isOnline = true" and then list only those users in a TableView. I believe this counts in firebase as 1 read for every registered account, even though the user is not online.
The app is already live in the AppStore and I have quite a few people registered already. About 300. But, to be honest a lot of people just try it once or a few times and then leave again and never use the app again. But every time someone wants to update the online users, they cycle through 300 users and this gives me 300 reads with firebase. Right now it's not a big problem, but once people really start to use the app, I will reach my quotum quite quickly.
I could try .addSnapshotListener , but this will refresh the user screen everytime something happens in the userlist. It will be too chaotic. I've read about reading data from cache, but I'm not sure how to go about this. I could also get a fresh userlist when the app starts, save it locally and check every now and then if there are new online users, but I want the list to be updated whenever the user wants to.
Is there a way to compare a locally saved list to the online database list and only read/get the documents that are changed / new?
Sorry for the long text. Hopefully anyone can help me out!
Thanks.
Below is my code to load the users. It's a bit messy sorry.. Basically it retrieves all users and sorts them by online and offline. If a user searches for another user, it takes a String "query" as input.
Code :
func loadAllAvailableUsers(query : String) {
availableEmails = []
availableUsers = []
onlineUsers = []
isInGameIndex = []
var av : [String] = []
var ae : [String] = []
var wins : [Int] = []
var losses : [Int] = []
var draw : [Int] = []
var matches : [Int] = []
let collection = db.collection("allUsers")
collection.getDocuments { (snapshot, err) in
if let e = err {
print(e.localizedDescription)
} else {
if let documents = snapshot?.documents {
print("found users")
for doc in documents {
print(doc.documentID)
if doc.documentID != currentEmail && query == "" {
print(doc.data()["isOnline"] as! Bool)
if let name = doc.data()["username"] as? String, let w = doc.data()["wins"] as? Int, let l = doc.data()["losses"] as? Int, let d = doc.data()["draw"] as? Int, let numOfMatches = doc.data()["numberOfMatches"] as? Int, let online = doc.data()["isOnline"] as? Bool {
print("adding user : \(name) to list")
if online {
matches.append(numOfMatches)
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
onlineUsers.append(name)
if doc.data()["isInGame"] as! Bool == true {
print(i)
self.delegate?.addToInGameIndex(name: name)
}
}
}
} else if query != "" {
if let name = doc.data()["username"] as? String, let w = doc.data()["wins"] as? Int, let l = doc.data()["losses"] as? Int, let d = doc.data()["draw"] as? Int, let online = doc.data()["isOnline"] as? Bool, let numOfMatches = doc.data()["numberOfMatches"] as? Int {
print("Searched : adding user : \(name) to list")
if doc.documentID == currentEmail {
continue
}
let lowerName = name.lowercased()
let lowerQuery = query.lowercased()
if lowerName.contains(lowerQuery) && online {
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
matches.append(numOfMatches)
if doc.data()["isInGame"] as! Bool == true {
print(i)
self.delegate?.addToInGameIndex(name: name)
}
onlineUsers.append(name)
} else if lowerName.contains(lowerQuery) && !online {
av.append(name)
ae.append(doc.documentID)
wins.append(w)
losses.append(l)
draw.append(d)
matches.append(numOfMatches)
}
}
}
}
}
if ae.count > 0 {
self.delegate?.reloadTheTable(wins: wins, losses: losses, draw: draw, ae: ae, au: av, matches: matches)
}
print(availableUsers)
}
}
}

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" : "cvvvv666#aol.com"
},
.
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))
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.

databaseReference.observe(DataEventType.value, with:{(DataSnapshot) not working properly all the time

func checkPaid(utilityId : String) -> Int{
var amount:String = ""
var status = 0
print("inside new function ")
print ("\(utilityId) inside new function ")
self.databaseRefPayment.observe(DataEventType.value, with:{(DataSnapshot) in
if DataSnapshot.childrenCount > 0 {
for payments in DataSnapshot.children.allObjects as! [DataSnapshot]{
var paymentsObject = payments.value as? NSDictionary
/*
if(paymentsObject!["month"] as! String == monthCheck && paymentsObject!["year"] as! String == monthCheck && paymentsObject!["utilityid"] as! String == utilityId as! String){ */
if(paymentsObject!["utilityId"] as! String == utilityId){
amount = paymentsObject!["amount"] as! String
print(amount)
print("Ypur program is working perfect")
status = 1
}
}
}
})
return status
}
The above function is filtering the data present in payments node based on the value for utilityId getting passed in the function . But the strange thing is observe(DataEventType.value, with:{(DataSnapshot) this event is not getting triggered all the time . Its just skipping that portion unnecessarily . I am very new to firebase and getting really mad with these kind of unpredicted behaviours . Please help me in this . feel free to ask for any clarifications .
The firebase executes firebase query functions in different thread , so after u call check paid(), it runs the checkpaid() firebase query in another thread,and it will return from the function , eventhough ur query is running in the background..so it will seem like,checkpaid() is not working , but actually it's running on another thread.
I think you first fetch all the required data from payment, and store it in a list , and then use that list to compare with utility.
Every time this function is called it adds/resets the Key-Value Observer for whichever child node you are observing it doesn't actually check the value unless it is changed. I believe it is your intention to call checkPaid(utilityId:) to check the child is 'paid' by some means. There is no need to add a KVO if you are directly reading the value for a single snapshot. consider the following:
func checkPaid(utilityId: String) -> Bool {
//Assume it is not paid if we cannot verify it.
var isPaid = false
//Create a new reference to Firebase Database
var ref: DatabaseReference!
ref = Database.database().reference().child(utilityId)
//Get the values for the child, test if it is paid or not.
ref.queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot) in
if (snapshot.value is NSNull) {
print("No Child With \(utilityId) Exists")
} else {
//child with utilityId exists, in case multiple utilityId's exist with the same value..
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let values = child.value as? [String : AnyObject] {
let uid = child.key //utilityId
var month:String = ""
var year:String = ""
var amount:String = ""
//var amount:Double = 0.0
//get values from parent
if let m = values["month"] as? String {
month = m
}
if let y = values["year"] as? String {
year = y
}
if let a = values["amount"] as? String {
amount = a
}
/*
if let a = values["amount"] as? Double {
amount = a
}
*/
//??
if ((month == monthCheck) && (year == monthCheck)) {
isPaid = true
}
}
}
}
return isPaid
}
I am making one assumption here; that utilityId is the key for the child.
if you have parent nodes to utilityId you'll have to transverse those as well when you reference the database:
ref = Database.database().reference().child(utilities).child(utilityId) ..etc
If you need a KVO to update a local property I suggest adding/calling it in viewDidLoad, it's completion handler should take care of updating whichever properties are updated when they change in Firebase.

Crash while retrieving data from Firebase [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
My Code
let ref = Database.database().reference().child("PostInfo")
let query = ref.queryOrdered(byChild: "post_title").queryEqual(toValue: self.retrieve_title)
query.observeSingleEvent(of: .value) { (snapshot) in
print((snapshot.childSnapshot(forPath: "status").value as? String)!)
}
}
jSon Data
{"PostInfo":{
"-KyjkkEAZeHLjdRLg20w" : {
"postImage" : "https://firebasestorage.googleapis.com/v0/b/hobbyquest-ee18d.appspot.com/o/8C40BA04-6D8D-4A23-B8BB-E1B3AC64E66F.png?alt=media&token=3f0f10e3-a64b-4187-9259-3c25bfc4a9e5",
"post_title" : "hahahahah",
"status" : "a banana an",
"threadForHobby" : "Bowling",
"userID" : "ccuvHt6feYVIO6GUXKo3OpO6VUn2"}
}
I am trying to get the status data from firebase but the app keeps crashing. Please help!
The problem is this line
(snapshot.childSnapshot(forPath: "status").value as? String)!
Your code is reading the data by .value.
.value returns ALL nodes within the given node and you will need to iterate over them. For example. Suppose our database looks like the following and you are querying for posts with a post_title of title_0
PostInfo
post_0
postImage = "www.thing.com"
status = "status_0"
post_title = "title_0"
post_1
postImage = "www.yipee.com"
status = "status_1"
post_title = "title_1"
post_2
postImage = "www.dude.com"
status = "status_2"
post_title = "title_0"
When running your query, both post_0 and post_2 will be returned because they both have title_0
You would need to iterate over the snapshot to get the results.
let ref = self.ref.child("PostInfo")
let query = ref.queryOrdered(byChild: "post_title").queryEqual(toValue: "title_0")
query.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let x = (snap.childSnapshot(forPath: "status").value as? String)!
print(x)
}
}
If you notice - your line of code works in this scenario because it's examining each child as a separate snapshot.
On the other hand, if you only want to return the first match, you can use .childAdded, which will return just an individual node:
let ref = self.ref.child("PostInfo")
let query = ref.queryOrdered(byChild: "post_title").queryEqual(toValue: "title_0")
query.observeSingleEvent(of: .childAdded) { (snapshot) in
let x = (snapshot.childSnapshot(forPath: "status").value as? String)!
print(x)
}
I don't know why but you could do it a different way but changing the layout of your data.
The data would be like this:
{"PostInfo":{
"searchidentifier" : { //whatever identifier you want, I think you're trying to use the post title
"object" : {
"postImage" : "https://firebasestorage.googleapis.com/v0/b/hobbyquest-ee18d.appspot.com/o/8C40BA04-6D8D-4A23-B8BB-E1B3AC64E66F.png?alt=media&token=3f0f10e3-a64b-4187-9259-3c25bfc4a9e5",
"post_title" : "hahahahah",
"status" : "a banana an",
"threadForHobby" : "Bowling",
"userID" : "ccuvHt6feYVIO6GUXKo3OpO6VUn2"}
}
}
And you would retrieve your data like this:
let ref = Database.database().reference().child("PostInfo").child("\(self.retrieve_title)")
ref.observe(.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for classes in snapshot.children.allObjects as![FIRDataSnapshot] {
let classesObject = classes.value as? [String: AnyObject]
let postImage = classesObject?["postimage"]
//Retrieve all the other objects here as well
}
}
})