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)
}
})
}
Related
I am trying to get values from my database. However, I am unsuccessful. Here is what the DB looks like:
Here is what I tried to do, to read the values:
taskRef = Database.database().reference(withPath: "Tasks").child(titleOfTask)
taskRef?.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let taskDict = snapshot.value as? [String:Any] {
print("Printing ---------> ",taskDict["Description"] as Any)
}
})
but nothing happens. Ideally, I want to read both the description in a different variable and due date into a different variable.
Important Things to know:
"Tasks" is.. you can think of it as a table name.
Alpha, Bravo, Jalabi... etc are all "Task Names" or childs of "Tasks"
Description, Due Date are the values
You're attaching a listener to /Tasks/$titleOfTask in your JSON, say /Tasks/Alpha. That listener is asking to be called for the first child node of /Tasks/Alpha, so the snapshot in your code will point to /Tasks/Alpha/Description. When you then print taskDict["Description"], you are printing the value of /Tasks/Alpha/Description/Description, which does not exist.
The simplest fix is to listen for the .value event:
taskRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if let taskDict = snapshot.value as? [String:Any] {
print("Printing ---------> ",taskDict["Description"] as Any)
}
})
Now the snapshot has all data from /Tasks/Alpha, and thus the taskDict["Description"] ends up being the value from /Tasks/Alpha/Description.
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)")
}
}
}
})
}
I've got the following database structure:
PVV
-- AutoID
- Data1
- Data2
- Status: Active
- ImageName: Path\FirebaseStorageImage.jpg
I'd like to retrieve the data in chronological order, and then sort the data in a descending manner (most recent first).
I think autoID does use a combination of date and time, and Firebase does normally retrieve the data in a fixed order. I am using the same function as below to retrieve text data (that does not have an imageName), and that works fine.
However, the function below returns data in a random order:
func LoadDataFromImageTest() {
self.ImageList.removeAll()
self.ImageTestFromFBTableView.reloadData()
databaseReference = Database.database().reference()
let refPVV = Database.database().reference(withPath: "PVV").queryOrdered(byChild: "Status").queryEqual(toValue: "Active")
refPVV.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self?.ImageList.removeAll()
//iterating through all the values
for PVV in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let PVVObject = PVV.value as? [String: AnyObject]
// let PVVText = PVVObject?["ImageString"]
let PVVName = PVVObject?["Name"]
let PVVBodyText = PVVObject?["BodyText"]
let PVVValue = PVVObject?["PVVValue"]
let Key = PVV.key
let PVVImageName = PVVObject?["ImageName"] as! String?
let imageURL = Storage.storage().reference().child(PVVImageName!)
imageURL.downloadURL(completion: { (url, error) in
if error != nil {
print(error?.localizedDescription as Any)
return
}
PVVurlName = url
let PVV = ImageModel(Name: PVVName as!String?, BodyText: PVVBodyText as! String?, PVVValue: PVVValue as! String?, Key: Key as String?, ImageName: PVVurlName as URL?)
self!.ImageList.insert(PVV, at: 0)
self?.ImageTestFromFBTableView.reloadData()
})
}
}
}
)}
I set a debug point right before I start downloading the URL. Each time I run, it returns values for PVVObject in a different order.
I have another tree like this:
Challenges
- AutoID
- Data1
- Data 2
- Status: Active
I've recycled the function above to retrieve data from the above tree, and I always get the data in the same order, when setting a debug point in the same place.
What am I doing wrong?
As per Firebase documentation the downloadURL method is asynchronous. It means that the order in which the downloaded files are retrieved is not guaranteed. When you are in the completion block of the downloadURL method, you have no idea to which PPV object the image belongs to.
I think the best is to change the architecture of your code. Create an object model class for PPV, with a imageUrl property (which is attached to each instance), and trigger the download job when you observe a change in value of this property (in the didSet method for instance). This way you will be sure that the downloaded file belongs to the instance.
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
I want to find the youngest user in my list of users and load their data: name, profile pict, and current job assignments. I have read the Firebase primer on querying data, but their examples don't work for me because my data is organized differently. I have an additional child layer.
This is my JSON tree in Firebase:
I've tried loading the list of users and then iterating over them to find the youngest user, but that seems like overkill. The Firebase documentation makes me think I should be able to do the query through a Firebase method, like 'queryOrderedByChild' or similar.
I've gone over the old documentation here and the new documentation here, but I'm still left wondering what to do.
So this is my workflow:
The app will find the youngest user in the list of "members" and load their name, profile pict, birthday, etc. They will choose from a list of jobs. Once that user has chosen from the lists of available jobs, the app will load the next youngest user from the list of "members", and so on until all users have been loaded and have been given the chance to select jobs.
I think a better workflow would be this:
Get youngest user by utilizing a Firebase query
Use that query to load that user (image and name)
How would I go about doing that?
EDIT #1: Code I've Tried
func loadExistingUsers(completion: #escaping ([[String : Any]]) -> ()) {
var dictionary = [[String : Any]]()
ref.child("members").observeSingleEvent(of: .value) { (snapshot: FIRDataSnapshot) in
for child in snapshot.children {
let snap = child as! FIRDataSnapshot
if let value = snap.value as? [String : Any] {
dictionary.append(value)
}
}
completion(dictionary)
}
}
And then in ViewDidLoad:
loadExistingUsers { (dictionary) in
var youngestBirthday = 19000101
var userName = "Sophie"
for item in dictionary {
let fetchedBirthday = item["birthday"] as! Int
let fetchedName = item["firstName"] as! String
if fetchedBirthday > youngestBirthday {
youngestBirthday = fetchedBirthday
userName = fetchedName
}
}
print(userName,youngestBirthday)
}
This method returns the youngest user from my list of users, but it seems like an awfully long way to go to get what I want. I have to first fetch the users from Firebase, and then parse the snapshot, then create an array, then sort the array, then get the user name. I was under the impression Firebase could do all that with one query. Am I wrong?
You can get the youngest child using this code: (since your youngest date is the largest number so I am using toLast)
ref.child("members").queryOrdered(byChild:"birthday").queryLimited(toLast: 1).observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["firstname"] as? String
//you can do for other values as well
print(name)
}
})