Accessing an array of data in Firebase using Swift? - swift

I'm working on a Swift app using Firebase Database. I have a structure like this:
"Groups" : {
"-KWewQiWc6QPfiq5X1AY" : {
"Members" : [ "emmaturner", "maxturner", "ethanturner" ],
"Teacher" : "ethanturner",
"Title" : "Kimbra Followers",
"groupId" : "-KWewQiWc6QPfiq5X1AY"
}
}
I'm trying to access the data in Members, in order to see if the user is in that Group.
I was previously using ref.child("Groups").queryOrdered(byChild: "Teacher").queryEqual(toValue: userId) However, I learned that this is only returning the group if the user is the Teacher. I tried "members/"
and "members/0" in place of Teacher, but of course these either do not work or only return access for the first user. How can I get Firebase to return the group as long as their name is mentioned in the array of group members?

Try this:
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
var membersArray = [String]()
ref.child("Groups").observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots
{
let teacher = snap.childSnapshot(forPath: "Teacher").value as! String
self.ref.child("Groups").child(snap.key).child("Members").observeSingleEvent(of: .value, with: { (member) in
if let members = member.children.allObjects as? [FIRDataSnapshot] {
for mem in members
{
print(mem.value as! String)
let currentMember = mem.value as! String
membersArray.append(currentMember)
}
}
if(membersArray.contains(teacher))
{
print(membersArray)
}
})
}
}
})

Related

queryOrdered doesn't always return data on tableViewCell in correct order

I am trying to order data on the notifications page from new to old based on timestamp, right now - when i run it, sometimes it is in the correct order but other times it is random and incorrect. Please let me know if there is anything i can add to make sure it runs smoothly at all times, thank you in advance :)
My firebase JSON structure is:
"notifications" : {
"BlP58dSQGCUBwhst91yha43AQu42" : {
"-LeNCQJ6nUSR1263iKyj" : {
"from" : "FRuuk20CHrhNlYIBmgN4TTz3Cxn1",
"timestamp" : 1557331817,
"type" : "true"
},
"-LeNCRwNpNaXm2qhYPpu" : {
"from" : "FRuuk20CHrhNlYIBmgN4TTz3Cxn1",
"timestamp" : 1557331824,
"type" : "true"
},
"BlP58dSQGCUBwhst91yha43AQu42-FRuuk20CHrhNlYIBmgN4TTz3Cxn1" : {
"from" : "FRuuk20CHrhNlYIBmgN4TTz3Cxn1",
"timestamp" : 1557331811,
"type" : "false"
}
},
My code:
func observeNotification(withId id: String, completion: #escaping (Notifications) -> Void) {
REF_NOTIFICATION.child(id).queryOrdered(byChild: "timestamp").observe(.childAdded, with: { snapshot in
if let dict = snapshot.value as? [String: Any] {
let newNoti = Notifications.transform(dict: dict, key: snapshot.key)
completion(newNoti)
}
})
}
Edit:
The function is then called in the NotificationViewController like this:
func loadNotifications() {
guard let currentUser = Api.User.CURRENT_USER else { return }
Api.Notification.observeNotification(withId: currentUser.uid , completion: { notifications in
guard let uid = notifications.from else { return }
self.fetchUser(uid: uid, completed: {
self.notifications.insert(notifications, at: 0)
self.tableView.reloadData()
})
})
}
and loadNotifications() is called in the viewDidLoad
UPDATE:
Trying to do it using "for child in snapshot.children" but nothing is showing on notifications page anymore
func observeNotification(withId id: String, completion: #escaping (Notifications) -> Void) {
REF_NOTIFICATION.child(id).observe(.value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let notificationOrder = self.REF_NOTIFICATION.child(key).queryOrdered(byChild: "timestamp")
notificationOrder.observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: Any] {
print(dict)
let newNoti = Notifications.transform(dict: dict, key: snapshot.key)
completion(newNoti)
}
})
}
})
}
}
The DataSnapshot that you get back from Firebase contains three types of information:
The key of each child that matched the query.
The value of each child that matched the query.
The order in which the children result from the query.
When you convert the entire result to a dictionary (dict = snapshot.value as? [String: Any]), there is only place for the keys and the values. So the order of the children gets lost.
To maintain the order of the child nodes, loop over the query results of snapshot.children as shown in the documentation on listening for lists of data with a value event.

Swift: Search for a specific value in Firebase Database & find all associated data

I have data uploading to my Database. I'm trying to implement Search functionality that I can search for something by the name, and if the name is found then autopopulate textfields with the data corresponding to that name. So for example if I search 'Pepsi Max' I want to find pepsi max in my database and then display the price/location/rating etc.
I currently have a Search function but that just searches the entire db and prints all values.
func searchT() {
let pub = pubName.text
print(pub)
let databaseRef = Database.database().reference().child("Drinks")
let query = databaseRef.queryOrdered(byChild: "pub").queryStarting(atValue: pub).queryEnding(atValue: "\(String(describing: pub))\\uf8ff")
query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else { return }
print(snapshot.value as Any)
DispatchQueue.main.async {
guard let dict = snapshot.value as? [String:Any] else {
print(snapshot)
return
}
let pubName = dict["pub"] as? String
let pubLocation = dict["location"] as? String
let price = dict["price"] as? String
let rating = dict["rating"] as? String
let comment = dict["comment"] as? String
self.pubName.text?.append(pubName!)
self.pubLocation.text?.append(pubLocation!)
self.price.text?.append(price!)
self.rating.text?.append(rating!)
self.comment.text?.append(comment!)
}
}
}
You will notice that in this function I'm searching by the data 'pubName' (which I think I'm setting incorrectly in the first line, but not sure how to correct it). This function crashes on the first line of setting the textViews to a value as there's 'nil while unwrapping an Optional value'
How can I search by pubName , locate the corresponding value and then set the textfields as the remaining data in the db relating to the searched value.
Thanks in advance, E
1. Realtime Database
Since you haven't included the structure of your database, I assume you have a database structure for drinks like below:
Screenshot of my Realtime database for this answer
{
"Drinks" : {
"-LYiUHm4vtrB3LqCBxEc" : {
"location" : "toronto",
"name" : "pepsi max",
"price" : 13.5,
"rating" : 3.6
},
"-LYiUHm5Lgt3-LENTdBZ" : {
"location" : "new york",
"name" : "diet coke",
"price" : 15.45,
"rating" : 5
},
"-LYiUHm5Lgt3-LENTdB_" : {
"location" : "chicago",
"name" : "mountain dew",
"price" : 2,
"rating" : 2
},
"-LYiUHm5Lgt3-LENTdBa" : {
"location" : "vancouver",
"name" : "sprite",
"price" : 6.98,
"rating" : 4.5
}
}
}
2. Swift 4.0
Now, to search any drink by name use below code:
func search(drinkName: String) {
let databaseRef = Database.database().reference().child("Drinks")
let query = databaseRef.queryOrdered(byChild: "name").queryStarting(atValue: drinkName).queryEnding(atValue: "\(drinkName)\\uf8ff")
query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else { return }
//print(snapshot.value)
DispatchQueue.main.async {
// Update TextFields here
}
}
}
The \uf8ff character used in the query above is a very high code point in the Unicode range. Because it is after most regular characters in Unicode, the query matches all values that start with a b.
Source: https://firebase.google.com/docs/database/rest/retrieve-data
Note: queryOrderedByChild() is case-sensitive. It is nice practice to save all fields lowercased in database as this makes it easier to query data. You can always format strings in front end.
3. Add ".indexOn" to Realtime Database's Rules
In order to above query to work and achieve better performance, you need to set the index on the field that you are going to search by.
You can do this by going to Rules tab and adding index like below:
{
"rules": {
".read": true,
".write": true,
"Drinks": {
".indexOn": "name"
}
}
}
Source: More information on indexing data
Updated Answer for your updated question:
func searchT() {
// You must cast pub variable as String.
guard let pub: String = pubName.text else { return }
print(pub)
let databaseRef = Database.database().reference().child("Drinks")
let query = databaseRef.queryOrdered(byChild: "pub").queryStarting(atValue: pub).queryEnding(atValue: "\(String(describing: pub))\\uf8ff")
query.observeSingleEvent(of: .value) { (snapshot) in
guard snapshot.exists() != false else {
print("failing here")
return }
print(snapshot.value as Any)
DispatchQueue.main.async {
guard let dict = snapshot.value as? [String:Any] else {
print(snapshot)
return
}
let pubName = dict["pub"] as? String
let pubLocation = dict["location"] as? String
let price = dict["price"] as? String
let rating = dict["rating"] as? String
let comment = dict["comment"] as? String
}
}
}

Firebase Swift 3 Fetching multiple values from a group

I want to be able to list users in a tableview by fetching their UID Value from a key stored in my database. As of now, the code only fetches the first value in the database rather than all of the values. Here is the code for fetching the applicants.
func loadApplicants() {
let usersRef = ref.child("users")
usersRef.observe(.value, with: { (users) in
var resultArray = [UserClass]()
for user in users.children {
let user = UserClass(snapshot: user as! DataSnapshot)
if user.uid == self.job.userID {
let appRef = self.ref.child("jobs").child(self.job.postID).child("applicants")
appRef.queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { (snapshot) in
let sValue = snapshot.value
resultArray.append(user)
})
}
}
}) { (error) in
print(error.localizedDescription)
}
}
This is what my database looks like where the User's UIDs are stored.
jobs
"Job ID"
applicants:
-KtLJaQnFMnyI-MDWpys:"8R6ZAojX0FNO7aSd2mm5aQXQFpk1"
-KtLLBFU_aVS_xfSpw1k:"GGqjtYvwSwQw9hQCVpF4lHN0kMI3"
If I was to run the app, it fetches UID: "8R6ZAojX0FNO7aSd2mm5aQXQFpk1"
How can I implement a for loop or an if statement to ensure that all of the values are taken and appended into the table view
I know that I need a for loop before the fetchApplicants is called from AuthService because it is only fetching one UID but I can't work out where it would go.
Thanks.
P.S. This is what I have tried
func loadApplicants() {
let jobID = job.postID
let appRef = ref.child("jobs").child(jobID!).child("applicants")
appRef.queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let applicants = snapshot.value! as? [String:AnyObject] {
for (value) in applicants {
self.authService.fetchApplicants(applicantID: "\(value!)", completion: { (users) in
self.usersArray = users
self.tableView.reloadData()
})
}
}
})
}
but the output is:
(key: "-KtLLBFU_aVS_xfSpw1k", value: GGqjtYvwSwQw9hQCVpF4lHN0kMI3)
(key: "-KtLJaQnFMnyI-MDWpys", value: 8R6ZAojX0FNO7aSd2mm5aQXQFpk1)
Needed to use observe instead of observeSingleEvent
Answer:
appRef.queryOrderedByKey().observe(.childAdded, with: { (snapshot) in

how to get the .child key in an array?

I want to take the save all of my blocked user child as a string in my code. But as simple as it might sound I was not able to figure it out.
static func block(myself: String, posterUID: String){
var blockedUserRef = Database.database().reference().child("users").child(myself).child("blockedUsers").child(posterUID)
blockedUserRef.setValue(true)
var blokedArray:[String] = []
var handle:DatabaseHandle!
blockedUserRef = Database.database().reference()
handle = blockedUserRef.child("blockedUsers").observe(.childAdded, with:{ (DataSnapshot) in
if let item = DataSnapshot.value as? String{
blokedArray.append(item)
}
})
}
I want to take the array of my blocked users go through in for loop and filter them from showing.
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshot = snapshot.children.allObjects as? [DataSnapshot]
else { return completion([]) }
// 3 filter the user from the array
let users =
snapshot
.flatMap(User.init)
.filter { $0.uid != currentUser.uid }
Here is my firebase:
"users" : {
"3YLVUDA8YnSuVYUTxv6cvOQPSjm1" : {
"username" : "🌸😍🌸"
},
"4duY2hhTv5S7sSXtZYBfiP7JBLz1" : {
"username" : "Love from NYC"
},
"58t0M2Fxxhg6GRT96vVbKMFHRKO2" : {
"blockedUsers" : {
"4duY2hhTv5S7sSXtZYBfiP7JBLz1" : true,
"SqarOdPJUydcdV6deXTeIdzkarE2" : true
},
"username" : "IStandWithYou"
},
some new ways I am trying I debugged it and it seems that it is not saving it onto the array
///adedblock
var blockedUserRef = Database.database().reference().child("users").child(currentUser.uid).child("blockedUsers")
var blokedArray:[String] = []
var handle:DatabaseHandle!
blockedUserRef = Database.database().reference()
handle = blockedUserRef.child("blockedUsers").observe(.childAdded, with:{ (DataSnapshot) in
if let item = DataSnapshot.value as? String{
blokedArray.append(item)
}
})
for user in blokedArray{
let users =
snapshot
.flatMap(User.init)
.filter { $0.uid != user }
}
This can be done by saving all of the child entries of blockedUsers to an array that will hold all your strings.
To do this, first create an empty array like
myList:[string] = []
Then, you need to create a reference to your Firebase database like:
var ref: DatabaseReference!
You also need a database handle to navigate around your database:
var handle:DatabaseHandle!
Now, you need to go to your ViewDidLoad function and add code like:
ref = Database.database().reference()
handle = ref?.child("blockedUsers").observe(.childAdded, with:{ (DataSnapshot) in
if let item = DataSnapshot.value as? String{
self.myList.append(item)
}
})
This piece of code is checking whether an entry is added to the child database "blockedUsers", and from that, it is adding whatever was just entered into the array.
Now, you have an array filled with whatever was in the child database, blockedUsers. You can now use the array in a UITableView, or list it in a label.
Hope this helped!

Firebase snapshot not being cast to dictionary

I have this function that is supposed to fetch data from a comments node from firebase. I want to implement pagination to not load 100+ comments at once. Everything seems to be working but my code seems to be failing at casting the snapchat.value to a Dictionary
func fetchComments(){
messagesRef = Database.database().reference().child("Comments").child(eventKey)
var query = messagesRef?.queryOrderedByKey()
if comments.count > 0 {
let value = comments.last?.commentID
query = query?.queryStarting(atValue: value)
}
query?.queryLimited(toFirst: 2).observe(.childAdded, with: { (snapshot) in
var allObjects = snapshot.children.allObjects as? [DataSnapshot]
allObjects?.forEach({ (snapshot) in
// print out snapshot and it isn't empty
print(snapshot.value) // here it keeps going into the else statement even though snapshot.value clearly exist.
guard let commentDictionary = snapshot.value as? [String:Any] else{
return
}
print(commentDictionary)
})
}) { (err) in
print("Failed to observe comments")
}
}
My question is can anyone take a look at this and maybe see where I went wrong? My code looks fine to me and I can't see what's is wrong.
My tree looks like this
"Comments" : {
"CCDS" : {
"-KrrsXkj6FznzRD0-Xzs" : {
"content" : "Shawn",
"profileImageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/profile_images%2FBC868F8F-E9EC-4B9D-A248-DD2187BC140C.PNG?alt=media&token=fb14700c-2b05-4077-b45c-afd3de705801",
"timestamp" : 1.503102381340935E9,
"uid" : "oxgjbrhingbf7vbaHpflhw6G7tB2"
}
},
"MIA" : {
"-Krghz9d5_CPjkmdffef" : {
"content" : "22",
"profileImageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/profile_images%2FF50F6915-DEAB-4A5B-B1AB-CABC1E349148.PNG?alt=media&token=4eb7c708-ec87-45bf-952d-0bd410faee50",
"timestamp" : 1.502915064803007E9,
"uid" : "oxgjbrhingbf7vbaHpflhw6G7tB2"
},
"-KrpoEnNYsmRZ5guORUj" : {
"content" : "23",
"profileImageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/profile_images%2FBC868F8F-E9EC-4B9D-A248-DD2187BC140C.PNG?alt=media&token=fb14700c-2b05-4077-b45c-afd3de705801",
"timestamp" : 1.503067700479352E9,
"uid" : "oxgjbrhingbf7vbaHpflhw6G7tB2"
}
}
}
Based off my code it bypasses the key and goes straight to the children.
For example if pass in MIA it should go to MIA and grab the key corresponding to each comment "-KrrsXkj6FznzRD0-Xzs" and "-KrpoEnNYsmRZ5guORUj" but it is returning everything under that unique ID instead. Which is a problem
The code in your callback seems to assume that you get called with a collection of comments. To get such a collection you need to observe the .value event. When you observe the .value event, you callback gets invoked with a single snapshot that contains all the nodes matching the query:
query?.queryLimited(toFirst: 2).observe(.value, with: { (snapshot) in
var allObjects = snapshot.children.allObjects as? [DataSnapshot]
allObjects?.forEach({ (snapshot) in
print(snapshot.key)
print(snapshot.value)
guard let commentDictionary = snapshot.value as? [String:Any] else{
return
}
print(commentDictionary)
})
}) { (err) in
print("Failed to observe comments")
}
When you observe .childAdded, your callback instead gets called for every individual node matching the query. That means you need to get rid of a loop in your code:
query?.queryLimited(toFirst: 2).observe(.childAdded, with: { (snapshot) in
print(snapshot.key)
print(snapshot.value)
guard let commentDictionary = snapshot.value as? [String:Any] else{
return
}
print(commentDictionary)
}) { (err) in
print("Failed to observe comments")
}