Query to count number followers swift 4 - swift

I'm trying to count the number of followers the user has from my firebase database but no success. The number of followers is returning any value. When a user follows another user it creates a node in firebase with the userID and a Int value of 1 as shown below.
fileprivate func countTrusting() {
guard let userId = self.user?.uid else { return }
guard let currentLoggedInUser = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("following").child(userId).queryOrderedByValue().queryEqual(toValue: 1).observe(DataEventType.value, with: { (snapshot) in
let count = String(snapshot.childrenCount)
self.trustedLabel.text = "\(count) \nFollowing"
}) { (error) in
print("There was an error getting the following count:", error)
}
}
My firebase database

If I understand the question, you want to have a count of the number of followers of a user. In that case, there's no need for a query as the only nodes that will exist are the users followers.
This code reads in the child followers nodes and prints the count.
let userFollowingRef = fbRootRef.child("following").child("some_user_uid")
userFollowingRef.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount)
})

Related

swift for loop order of data is not right

I want to fetch data from firebase and put them in an array. The first part of the function is always in the right order, i can see it when i print(DEBUG(files). But after for loop, the order of the documents messes and i always get random order. Shouldn't i always get the same order?
func getUnreadMessages(){
guard let uid = AuthViewModel.shared.userSession?.uid else {return}
Firestore.firestore().collection("users").document(uid).collection("chats").order(by: "created", descending: true).getDocuments { (snapshot, _) in
guard let files = snapshot?.documents.compactMap({ $0.documentID }) else {return}
print("DEBUG: \(files)")
for file in files{
Firestore.firestore().collection("users").document(uid).collection("chats").document(file).collection("messages").whereField("read", isEqualTo: false).getDocuments { (snapshot, _) in
guard let documents = snapshot?.documents.compactMap({ $0.documentID }) else {return}
print("DEBUG: \(documents)")
self.count.append(documents.count)
print("DEBUG: \(self.count)")
}
}
}
}
You get a different order of results because while you call the database in the correct order, there is no guarantee that the database will return your call in that same order, because some calls take longer than other calls. I think the simplest solution is to record the original order, attach it to the data in your second call (where you determine document count), and sort the collection (the array) by that original order.
The easiest way to attach this index value to the document count is a custom model:
struct MessageCount {
let count: Int // this is the message count you're after
let n: Int // this is the index of the original order
init(count: Int, n: Int) {
self.count = count
self.n = n
}
}
Then just use a dispatch group to coordinate the async tasks and in the completion of the dispatch group, sort the array by index and you will have an array of message counts in the intended order:
func getUnreadMessages() {
guard let uid = AuthViewModel.shared.userSession?.uid else {
return
}
let db = Firestore.firestore() // instantiate it once since it could be created hundreds or thousands of times in this function
db.collection("users").document(uid).collection("chats").order(by: "created", descending: true).getDocuments { (snapshot, error) in
guard let snapshot = snapshot,
!snapshot.isEmpty else {
if let error = error {
print(error) // you oddly omitted the error in your code, never do that
}
return
}
let dispatch = DispatchGroup() // set up the dispatch group outside the loop
var messageCounts = [MessageCount]() // this temp array will carry the data with the index
// to record the original order of the loop, just enumerate it and access `n` (the index)
for (n, doc) in snapshot.documents.enumerated() {
dispatch.enter() // enter dispatch on each iteration
db.collection("users").document(uid).collection("chats").document(doc.documentID).collection("messages").whereField("read", isEqualTo: false).getDocuments { (snapshot, error) in
if let snapshot = snapshot {
let c = snapshot.count // get the message count
let count = MessageCount(count: c, n: n) // add it to the model along with n which is captured by the parent closure
messageCounts.append(count) // append to our temp array
} else if let error = error {
print(error)
}
dispatch.leave() // leave dispatch no matter the outcome
}
}
// this is the completion handler of the dispatch group
dispatch.notify(queue: .main) {
// sort the array by index and then map it to just get the message counts
let counts = messageCounts.sorted(by: { $0.n < $1.n }).map({ $0.count })
}
}
}
The order of returned results are determined by an order(by clause. Otherwise the results may seem somewhat random.
In this case the first Firebase call specifies an order, so those documents will always be returned in the correct order.
collection("chats").order(by: "created"
But the next firebase call does not specify an order so, the returned documents may be somewhat inconsistently ordered.
.collection("messages").whereField
We need to have some way to guarantee that order.
Suppose the structure is this
chats (collection)
user ids (documents)
chats (collection)
chat ids (documents)
messages (collection)
message ids (documents that you want ordered)
the message id's would need to have a field to order them by - call that ordering
Here's the code that prints the count of the number of messages in each chat id and then prints the messages in order
func getUnreadMessages() {
let uid = "uid_0"
self.db.collection("users_chats").document(uid).collection("chats").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 ref = doc.reference.collection("messages")
ref.order(by: "ordering").getDocuments(completion: { messagesSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let messages = messagesSnapshot?.documents else { return }
print("the chat document: \(doc.documentID) has \(messages.count) messages")
for msg in messages {
let order = msg.get("ordering")
let msg = msg.get("read")
print("order: \(order!)", "is read: \(msg!)")
}
})
}
})
}
if there were three messages in chat 0, the output looks like this
the chat document: chat_0 has 3 messages
the chat document: chat_1 has 0 messages
the chat document: chat_2 has 0 messages
order: 0 isRead: 0
order: 1 isRead: 1
order: 2 isRead: 0

Struggling To Query Using getDocuments() in Firestore Swift

This is the first time I am using a Firestore Query and I'm struggling to parse the data. I normally use the same setup when I get documents (which works), but when I attach it to a query it does not work.
I am trying to query the database for the shop most visited, so I can later set it as favourite.
My Code:
func findFavouriteShop(completed: #escaping ([String]) -> Void)
{
// Variables
let dispatch = DispatchGroup()
var dummyDetails = [String]()
// References
let db = Firestore.firestore()
let userID = Auth.auth().currentUser?.uid
let groupCollectionRef = String("visits-" + userID! )
// Query the database for the document with the most counts
dispatch.enter()
db.collectionGroup(groupCollectionRef).order(by: "count", descending: true).limit(to: 1).getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
print(snapshot)
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
// Start Assignments
let shopName = data["shopName"] as? String
let count = data["count"] as? String
// Append the dummy array
dummyDetails.append(shopName!)
dummyDetails.append(count!)
}
dispatch.leave()
}
dispatch.notify(queue: .main, execute: {
print("USER number of documents appended: \(dummyDetails.count)")
completed(dummyDetails)}
)
}
Using Print statements it seems as if the guard statement kicks the function out. The processor does not reach the for-loop to do the assignments. When I print the snapshot it returns an empty array.
I am sure I have used the wrong notation, but I'm just not sure where.
There's a lot to comment on, such as your choice of collection groups over collections (maybe that's what you need), why you limit the results to one document but feel the need to query a collection, the naming of your collections (seems odd), the query to get multiple shops but creating a function that only returns a single shop, using a string for a count property that should probably be an integer, and using a string array to return multiple components of a single shop instead of using a custom type.
That said, I think this should get you in the right direction. I've created a custom type to show you how I'd start this process but there's a lot more work to be done to get this where you need it to be. But this is a good starting point. Also, there was no need for a dispatch group since you weren't doing any additional async work in the document parsing.
class Shop {
let name: String // constant
var count: Int // variable
init(name: String, count: Int) {
self.name = name
self.count = count
}
}
func findFavouriteShops(completion: #escaping (_ shops: [Shop]?) -> Void) {
guard let userID = Auth.auth().currentUser?.uid else {
completion(nil)
return
}
var temp = [Shop]()
Firestore.firestore().collection("visits-\(userID)").order(by: "count", descending: true).limit(to: 1).getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
completion(nil)
return
}
for doc in snapshot.documents {
if let name = doc.get("shopName") as? String,
let count = doc.get("count") as? String {
let shop = Shop(name: name, count: count)
temp.append(Shop)
}
}
completion(temp)
}
}
You can return a Result type in this completion handler but for this example I opted for an optional array of Shop types (just to demonstrate flexibility). If the method returns nil then there was an error, otherwise there are either shops in the array or there aren't. I also don't know if you're looking for a single shop or multiple shops because in some of your code it appeared you wanted one and in other parts of your code it appeared you wanted multiple.
findFavouriteShops { (shops) in
if let shops = shops {
if shops.isEmpty {
print("no error but no shops found")
} else {
print("shops found")
}
} else {
print("error")
}
}

Firebase swift not retrieving all child values

I have a piece of code inside my Swift built iOS app, to retrieve all the nodes from a Firebase Realtime database. When I execute the code below I've noticed that it does not return all the child nodes.
When I query the particular nodes which are not being returned individually, at first the code returns 'nil' and then on a second attempt retrieves the nodes. (without doing any code changes in the process). Following this process, the node starts to show up in the results with the retrieve all nodes function.
Example 1: First returns nil, then on a second attempt returns the node. Which I can see from the console and definitely exists on the database.
ref?.child("transactions").child(email).child("14526452327").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
print(value)
print("!!****************!!")
// ...
}) { (error) in
print(error.localizedDescription)
}
The following is being used to retrieve all child values; at first this doesn't get all the nodes, however after running the code from Example 1 (twice) it starts to return the node in question.
ref?.child("transactions").child(email).observeSingleEvent(of: .value, with: { (snapshot) in
let childrenCount = snapshot.childrenCount
var counter : Int = 0
for trans in snapshot.children.allObjects as! [DataSnapshot]
{
counter = counter + 1
self.ref?.child("transactions").child(email).child(trans.key).observeSingleEvent(of: .value, with: { (snapshot2) in
I've also checked my Firebase query and data limits and I am nowhere near the threshold for the free account. Any help is greatly appreciated.
Try this:
func getData() {
// Making a reference
let transactionRef = Database.database().reference(withPath: "transactions")
transactionRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Printing the child count
print("There are \(snapshot.childrenCount) children found")
// Checking if the reference has some values
if snapshot.childrenCount > 0 {
// Go through every child
for data in snapshot.children.allObjects as! [DataSnapshot] {
if let data = data.value as? [String: Any] {
// Retrieve the data per child
// Example
let name = data["name"] as? String
let age = data["age"] as? Int
// Print the values for each child or do whatever you want
print("Name: \(name)\nAge: \(age)")
}
}
}
})
}

having problems in adding more than 1 information in the same child firebase

I want to achieve this:
This is my current database. It only shows 1 information but it should be showing 3 messages:
// loading the info onto firebase database
let uid = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("workout").observeSingleEvent(of: .value, with: { (snapshot) in
print("Got Snapshot")
print(snapshot.childrenCount)
let chilidCount = snapshot.childrenCount
print(chilidCount)
let post:[String:String] = ["\(chilidCount + 1)": textField.text!]
print(post)
ref.child("workout").child(uid!).setValue(post)
})
self.tableView.reloadData()
This is my code so far. I tried looking at other previous question from StackOverflow and also looked at firebase documentation but could not find anything useful.
This is my tableview
Try making a dictionary of the values you want to upload to your FIR Database.
I assume you want to upload the values to your database in a "workout" folder, and in that upload values for each user. You should do the following:
let uid = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
//Reference to the location where the messages get saved to
let userWorkoutRef = ref.child("workout").child(uid!)
userWorkoutRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Get the number of messages
let messagesCount = snapshot.childrenCount
//Making a dictionary: the key is the current number of messages plus one, the value is the current text entered in the text field
let valueToUpload = ["\(messagesCount + 1)": textField.text!]
//Uploading the dictionary to the database
userWorkoutRef.updateChildValues(valueToUpload) { (err, ref) in
if err != nil {
print(err!.localizedDescription)
return
} else {
print("success uploading data to db!")
}
}
}

How to get parent from child

So let's say I found that a value in database matches value entered by user through query. How can I find the parent of that value?
Ex. Let's say I enter in 35 as the age. 35 is also found in the database so how do I get the parent of that value (0)? Underlined in the picture.
I saw some similar questions asked but I can't seem to find a right answer to my question. Additionally, most of them are in different language.
Here is what I got so far:
#IBAction func onDiagnose(_ sender: Any) {
let ref1 = Database.database().reference(fromURL: "https://agetest.firebaseio.com/")
let databaseRef = ref1.child("data")
databaseRef.queryOrdered(byChild: "age").queryEqual(toValue: Int(ageTextField.text!)).observeSingleEvent(of: .value, with: { (snapshot) in
// if there is data in the snapshot reject the registration else allow it
if (snapshot.value! is NSNull) {
print("NULL")
} else {
//print(snapshot.value)
//get parent
//snapshot.ref.parent?.key! as Any
}
}) { (error) in
print(error.localizedDescription)
}
}
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
So you need to loop through the child nodes of the resulting snapshot to get at the individual results:
databaseRef.queryOrdered(byChild: "age").queryEqual(toValue: Int(ageTextField.text!)).observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.key)
}
}) { (error) in
print(error.localizedDescription)
}
Also see listening to value events for lists of data in the Firebase documentation.