In my app, I would regularly have a JSON topic, for example message, then nested in that is a random ID, then the message text as a string inside the random ID. But, I need to decipher multiple levels of random IDs. Is that possible in Firebase for Swift? This is what I mean:
This is my code:
Database.database().reference().child("app").observe(.childAdded) { (snapshot) in
//app is first in the JSON tree
let dict = snapshot.value as! [String: Any]
let msg = dict["message"] as! String
Obviously this is crashing the app, as it's looking for "Message" in the first RandomID. Is there a solution to this? I haven't found resources for specifically what I'm looking for. Thank you.
You'll want to loop over the children of snapshot as shown here:
Database.database().reference().child("app").observe(.childAdded) { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot //downcast
let dict = snap.value as! [String: Any]
let msg = dict["message"] as! String
}
})
Also see:
Get data out of array of Firebase snapshots
Swift Firebase -How to get all the k/v when using queryOrdered(byChild: ).queryEqual(toValue: )
other questions about looping over child nodes
Related
I have a Firebase Database whereby I'm trying to display 5 random users (with their name and email) by querying them by their age, on 5 different view controllers which are controlled by a UIPageViewController.
I have created the query code to display users by their age. The code is below:
func findoutinfo(){
let usersRef = Database.database().reference().child("Users")
let query = usersRef.queryOrdered(byChild: "Age").queryEqual(toValue: "21")
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let dict = childSnap.value as! [String: Any]
let name = dict["Name"] as! String
let email = dict["email"] as! String
print(childSnap.key, name, email)
}
})
}
However, I haven't discovered a way of randomly picking 5 users and display their information on the various pages. Ensuring that all of them are different.
Unfortunately. Firebase doesn't provide you with a simple solution for picking random children. Depending on the use case you might have to go for different approaches.
If the list of Users isn't big, you can get all the users, put them in an array and then pick 5 of them randomly. Something like:
var selectedUsers = [[String: Any]]()
let qtyOfUsersToSelect = 5
var users = getUsersArrayFromFirebase() // Made up function where you should fetch the Users.
for i in 1...5 {
selectedUsers.append(users.remove(at: Int.random(0..<users.count)))
}
If you expect the database users to be big, then I would recommend you using Firebase functions and handling this on the backend.
I'm currently retrieving data from a firebase database, and storing the data in a dictionary. When I try to list the keys in the dictionary like this: snapDict?.keys the indexes of the elements aren't the same as how they are in the database.
Database.database().reference().child("\(UserData().mySchool!)/posts").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.childrenCount)
let snapDict = snapshot.value as? [String: Any]
print(snapshot.value!)
let names = snapDict?.keys
print(names!)
for id in names! {
self.searchNames(id: id)
self.tableView.reloadData()
}
})
This is what the elements that will be in the dictionary look like in the database:
So, you would think when they are put into the dictionary they would be printed as -LJRUC8n........-LOF6JUdm-onVuaq-zij?
snapDict?.keys
prints:
["-LOBSAv_l5_x1xnKwx3_", "-LJRUC8nPF3Vg-DDGiYQ", "-LOBLXpTs39yLZo6EnHl", "-LOF6JUdm-onVuaq-zij", "-LODhXPQi8G7MX1bSfeb", "-LJaUiEnGOcBjKsTWSCS", "-LOBLZzrLAlzkhoidnKf"]
I can't figure out the order/pattern here. Alphabetical? Any ideas why the order turns out this way?
The keys in a dictionary are by definition unordered. So when you convert the snapshot to a dictionary, any information on the order of the nodes is lost.
On top of that, you don't specify an order before reading the data.
To fix both:
Database.database().reference()
.child("\(UserData().mySchool!)/posts")
.queryOrderedByKey()
.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.childrenCount)
for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
print(child.value)
}
})
Hello & thanks in advance, using swift 4- After retrieving my Firebase data, i am trying to group everything by Auto generated child ID- the (LKQvTZIe...) parts. My reason for doing this is because the "image, planit, title" all represent one complete post. I need to display the links in a collection view that is tied with the "title" and "planit" parts of the post, so each auto generated ID needs to be tied together.
My question - Is there any way to group or call things by those Auto generated ID's?
That seems like a logical way for me to assure that all 3 of these nodes are grouped together.
THIS IS THE VIEW DID LOAD SECTION
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference()
//ref.child("planits").observeSingleEvent(of: .value, with: { (snapshot) in
ref.child("planits").observe(.value, with: { (snapshot) in
let FirebaseDataDict = snapshot.value as! [String: AnyObject]
print(FirebaseDataDict)
// THIS GRABS THE EXACT USERS PLANIT DETAILS FROM THE DATA BASE
for child in snapshot.children { //.value can return more than 1 match
let snap = child as! DataSnapshot
let dict = snap.value as? NSDictionary
let senderID = dict!["senderId"]
// IF USER ID EQUALS THE SENDER ID IN DATABASE, THEN UPLOAD THEIR PLANITS
if userID == senderID as! String {
let titleOfPlanit = dict!["title"] as! [String]
let imagesForThisPlanit = dict!["images"] as! [String]
let individualPlanName = dict!["plans"] as! [String]
imagesInPlanit = imagesForThisPlanit
self.nameOfSinglePlan = individualPlanName
self.nameOfThisPlanit.append(contentsOf: titleOfPlanit)
}
}
})
// JSON RESPONSE, FOR INSTANCE - THE FIRST 3 BLOCKS OF CODE BELONG TO A SINGLE PLANIT, THE NEXT 3 BELONG TO THEIR OWN SEPERATE PLANIT
["marketing party "]
["https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F34047334%2F203533422144%2F1%2Foriginal.jpg?h=200&w=450&auto=compress&rect=234%2C0%2C734%2C367&s=26d86a54d46bcbe86ee2a2728f0b89b4", "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F42440910%2F68964867283%2F1%2Foriginal.jpg?h=200&w=450&auto=compress&rect=0%2C318%2C4592%2C2296&s=3aa7bf43a9a2c647e92a6aa5d11ce1bc"]
["Digital Marketing Training in Pomona,CA-USA|Eduscil", "SoulfulofNoise The Tour # Pomona"]
["send "]
["https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F45161425%2F210370190852%2F1%2Foriginal.jpg?h=200&w=450&auto=compress&rect=0%2C4%2C1920%2C960&s=b575da96cc7c5b13b36ba82e666e693a"]
["RRB Dance Company Parivaar Night"]
I do not have any experience in Swift but here's what you should do with Firebase about collecting posts:
When you request a child or child, you can ask for a uid for this posts
Now you can order the parts very normally
If you understand in Android or Java, the calls are as :
ref.child("planits").child(Auth.auth().currentUser?.uid).
ref.child("planits").child(senderid).
You can not post any value with the same name
FireBase creates random names for each new child
If you intend to create a child with the same name in a child, I find that is not possible
You can assemble the rest of the parts in order by Query
My Data in Firebase looks like this:
I am retrieving the data with
var ref: DatabaseReference
ref = Database.database().reference()
ref.child("peopleReminders").child("-LBO0TMbOM0cwd5TMMiP").observe(.value) { snapshot in
for value in snapshot.children {
print(value)
}
}
This prints the following:
Snap (0) r04
Snap (1) r02
Snap (2) r01
This should be an array. What I need to do is get the values R04, R02, and R01 out of it.
You don't need a loop or enumeration to parse it into an array as Firebase supports both Array and Dictionary. You can directly cast the result into an array. Below piece of code will work:
ref.child("peopleReminders").child("-LBO0TMbOM0cwd5TMMiP").observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
let result: [String] = snapshot.value as? [String] ?? []
print(result)
}
}
Note: Always check snapshot existence it will be false if data doesn't exist for the requested reference. Use observeSingleEvent when you need to fetch the data for once if you use observe the call back will be call many times whenever there is a change in requested node so that delete, insert, update etc.
I replaced for value in snapshot.children with the following code.
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? DataSnapshot {
print(rest.value!)
}
The new output is now:
R04
R02
R01
So this is my Firebase Structure:
I'm trying to get all books pictures (bookImage), add them to list and then use this list to fill a table or anythings else. (I'm using swift 3)
struct item {
let picture: String!}
var items = [item]()
func getLatestAddedItems(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").observe(.childAdded, with: {
FIRDataSnapshot in
let picture = (FIRDataSnapshot.value as? NSDictionary)?["bookImage"] as? String ?? ""
//self.items.insert(item(picture: picture), at: 0)
self.items.append(item(picture: picture))
print(self.items[0].picture)
print(self.items[1].picture) // error here
})}
I'm able to see the first print output but on the second one I'm getting fatal error: Index out of range even I have 3 books on my database.
Since your using .childAdded, it iterates through that closure for each object in the data tree, in this case, each book. When you try to print the second picture, its still in its first iteration. Meaning you only have retrieved the first book so far. That's why you can print the first book item but not the second one. If you moved the print statements outside of the closure, and then did the print statements after the closure iterated over all three books, you wouldn't get the error.
Don't change it to .value unless if every time a new one is subsequently added you want to get the entire list of books all over again. If its a large amount of books, it will be a lot of data to go through each time.
Summary: .childAdded gives you one book at a time, with a new snapshot for each one. .value gives you all the books in one snapshot, then you must iterate over them yourself in the closure. ex.
for snap in snapshot.children {
// now you can do something with each individual item
}
also I just noticed your using the FIRDataSnapshot type in your closure, that should be a variable which represents the snapshot you received, not the type itself. Change "FIRDataSnapshot in" to something like "snapshot in" snapshot is a representation of what information was given to you by the observe closure, in this case, an object with a type of FIRDataSnapshot.
Edit:
Your solution you mentioned below works fine, but I'll add an alternative that is cleaner and easier to use.
add an init method to your Book class that takes a FIRDataSnapshot as the init parameter, then init the object when you query Firebase:
struct Book {
let bookImageString: String
init?(snapshot: FIRDataSnapshot) {
guard let snap = snapshot.value as? [String : AnyObject], let urlString = snap["bookImage"] else { return nil }
bookImageString = imageString
{
{
then when you query firebase you can do this:
for snap in snapshot.children {
if let snap = snap as? FIRDataSnapshot, let book = Book(snapshot: snap) {
self.items.append(book)
{
}
doing it this way cleans up the code a little bit and leaves less chance of error in the code.
Also, since your using .value, make sure to empty the data source array at the beginning of the closer, or else you will get duplicates when new books are added.
items.removeAll()
Finally I'm posting the solution:
func getLatestAddedItems(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Items").observe(.value, with: {
snapshot in
//self.items.insert(item(picture: picture), at: 0)
for childSnap in snapshot.children.allObjects {
let snap = childSnap as! FIRDataSnapshot
print(snap.key)
let picture = (snap.value as? NSDictionary)?["bookImage"] as? String ?? ""
print(picture)
}
})
}