I'm having issue getting data from Firebase.
schema is
{
title: "dog",
images: {
main: "dog.png",
others: {
0: "1.png",
1: "2.png",
2: "3.png"
}
}
}
how can i parse FDataSnapshot to swift model??
Firebase is a NoSQL JSON database and has no schema and no tables. Data is stored with a 'tree' structure with nodes; parents and children.
You don't need to parse Firebase JSON data to access it, you can access it directly.
FDataSnapshots contain a .key, which is it's parent key in Firebase and .value. .Value may contain one node, or multiple nodes. The Value will have key:value pairs representing the data within the snapshot
So for your example you will have a Firebase structure like this
dogs
dog_id_0
title: "dog"
type: "Alaskan Malamute"
images:
main: "dog.png"
others:
0: "1.png"
1: "2.png"
dog_id_1
title: "another dog"
type: "Boxer"
images:
main: "another_dog.png"
others:
0: "3.png"
1: "4.png"
So, say you want to read in each dog_id_x node one at a time and print some values.
var ref = Firebase(url:"https://your-app.firebaseio.com/dogs")
ref.observeEventType(.ChildAdded, withBlock: { snapshot in
println(snapshot.value.objectForKey("title"))
println(snapshot.value.objectForKey("type"))
})
This will output
dog
Alaskan Malamute
another dog
Boxer
The dog_id_0 and dog_id_1 are node names created with the Firebase childByAutoId.
You could just as easily create a Dog class, and pass it the FDataSnapshot which will populate the class from the data within the snapshot.
February 2017 Update, Swift 3 Xcode 8
Since a lot of things with Swift3 and Firebase have changed by the time this question was asked I will provide an updated way to parse Firebase data:
let userID = FIRAuth.auth()?.currentUser?.uid
//I am registering to listen to a specific answer to appear
self.ref.child("queryResponse").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
//in my case the answer is of type array so I can cast it like this, should also work with NSDictionary or NSNumber
if let snapshotValue = snapshot.value as? NSArray{
//then I iterate over the values
for snapDict in snapshotValue{
//and I cast the objects to swift Dictionaries
let dict = snapDict as! Dictionary<String, Any>
}
}
}) { (error) in
print(error.localizedDescription)
}
Try to play with this:
func makeItems(from snapshot: DataSnapshot) -> [SimpleItem] {
var items = [SimpleItem]()
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let item = SimpleItem(parentKey: snap.key, dictionary: postDictionary)
items.append(item)
}
}
}
return items
}
func loadItems() {
firebaseService.databaseReference
.child("items")
.queryOrdered(byChild: "date")
.queryLimited(toLast: 5)
.observeSingleEvent(of: .value) { snapshot in
let items = self.makeItems(from: snapshot)
print("🧀 \(items)")
}
}
class SimpleItem {
var parentKey: String?
var id: String?
var description: String?
init(parentKey: String, dictionary: [String : AnyObject]) {
self.parentKey = parentKey
id = dictionary["id"] as? String
description = dictionary["description"] as? String
}
}
You could parse it maually with Dictionary or you can use my library.
Example code for your case:
func main(){
let root=SnapshotParser().parse(snap: Snapshot, type: Root.self)
}
class Root: ParsableObject {
var title:String?=nil
var images:Images?=nil
required init(){}
func bindProperties(binder: SnapshotParser.Binder) {
binder.bindField(name: "title", field: &title)
binder.bindObject(name: "images", field: &images)
}
}
class Images: ParsableObject {
var main:String?=nil
var others:[Int:String]?=nil
required init(){}
func bindProperties(binder: SnapshotParser.Binder) {
binder.bindField(name: "main", field: &main)
binder.bindDictionary(name: "others", dict: &others)
}
}
This will parse all the snapshot children in the single object and convert it in array and you can easily parse the array of children with index
if let snap = snapshot.children.allObjects as? [DataSnapshot]{
print(snap)
for (index,val) in snap.enumerated(){
print("values")
print(val)
print(val.value)
}
}
Related
I've spend hours looking at identical questions but none of the answers I've found are helping this issue. Simple app retrieves data from Firebase Database and passes to another view controller from the tableview. The main data will pass through but I can't edit the information without an identifying "key" which I tried to set as childByAutoID() but then changed to a timestamp. Regardless of the method, all I get is the entries info not the actual key itself.
func loadData() {
self.itemList.removeAll()
let ref = FIRDatabase.database().reference()
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
if let todoDict = snapshot.value as? [String:AnyObject] {
for (_,todoElement) in todoDict {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
print (snapshot.key);
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
If your data looks like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”
}
}
}
Then I would query like this:
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let child = child as? DataSnapshot
let key = child?.key as? String
if let todoElement = child?.value as? [String: Any] {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
self.tableView.reloadData()
}
}
})
Additionally, like I said in my comment you can just upload the key with the data if you’re using .updateChildValues(). Example:
let key = ref.child("userID!").childByAutoId().key
let feed = ["key": key,
“itemName”: itemName] as [String: Any]
let post = ["\(key)" : feed]
ref.child("userID").child("MyStuff").updateChildValues(post) // might want a completionBlock
Then you can get the key the same way you are getting the rest of the values. So your new data would look like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”,
key: “autoID”
}
}
}
The key you are trying to look for is located in the iterator of your for loop
Inside your if-let, try to do this:
for (key,todoElement) in todoDict {
print(key) // this is your childByAutoId key
}
This should solve the problem. Otherwise show us a screen of your database structure
Here is my data structure:
{ "ItemData": {
"Item": [
{
"name": "Table",
"measurement ": [
{
"height": 30
},
{
"width": 50
}
]
}
]
}
}
I can currently fetch all the data from Firebase and able to display the name on to a tableView. I am now trying to get the values that are nested inside the 'measurement' i.e. 'height' & 'width'. I have looked at Query Firebase for nested child swift, What's the best way of structuring data on firebase?, Iterate through nested snapshot children in Firebase using Swift and Firebase Swift 3 Xcode 8 - iterate through observe results but still have no clue how to solve this.
This is my Item class:
class Item {
var name: String!
var measurement: String!
var key: String
init(from snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as? [String: Any]
self.name = snapshotValue!["name"] as! String
self.measurement = snapshotValue?["measurement"] as! String
self.key = snapshot.key
}
}
This is the function I use to fetch the item. The ItemManager is a class that has the function to remove and add the array of Item:
func fetchItem() {
let databaseRef = FIRDatabase.database().reference(withPath: "ItemData/Item/")
databaseRef.observe(.value, with: { snapshot in
ItemManager.shared.removeAll()
for item in snapshot.children {
guard let snapshot = item as? FIRDataSnapshot else { continue }
let item = Item(from: snapshot)
ItemManager.shared.additem(item)
print(snapshot)
}
self.tableView.reloadData()
})
}
Please help me if you can :)
As suggested in comment measurement array of dictionary not the String, So if you want to get height and width from it you can get it this way.
class Item {
var name: String!
var heightMeasurement: String!
var widthMeasurement: String!
var key: String
init(from snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as? [String: Any]
self.name = snapshotValue!["name"] as! String
if let array = snapshotValue["measurement"] as? [[String:Any]],
let heightDic = array.first, let height = heightDic["height"],
let widthDic = array.last, let width = widthDic["width"] {
self.heightMeasurement = "\(height)"
self.widthMeasurement = "\(width)"
print(height, width)
}
else {
self.heightMeasurement = "" //set some default value
self.widthMeasurement = "" //set some default value
}
self.key = snapshot.key
}
}
Note: If your array having more than two objects than to get the height and width you need to subscripting the array index first to get the dictionary and then access its key according to get your value.
So I am currently trying to take data from my Firebase database and set it as its own variable, but the child for each chart is a specific date and time (yy.mm.dd.h.m.s). So i have an array storing all the dates I need, but i cant reference them when calling my snapshot. I've tried these two methods which throw the error "(child:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''"
var postCollection = [170802120618, 170802101427] //yy.mm.dd.hh.mm.ss
ref.child("users").child(uid!).child("Posts").child(self.postCollection[indexPath.row]).observe(.value, with: { (snapshot) in
for item in snapshot.children{
let snapshotValue = snapshot.value as? NSDictionary
let firstNameSnap = snapshotValue?["First Name"] as? String ?? ""
currentCell.nameLabel.text = firstNameSnap
}
})
and
var postCollection = [170802120618, 170802101427] //yy.mm.dd.hh.mm.ss
let selection = self.postCollection[indexPath.row]
ref.child("users").child(uid!).child("Posts").child(self.postCollection[indexPath).observe(.value, with: { (snapshot) in
for item in snapshot.children{
let snapshotValue = snapshot.value as? NSDictionary
let firstNameSnap = snapshotValue?["First Name"] as? String ?? ""
currentCell.nameLabel.text = firstNameSnap
}
})
And the Database chart being roughly:
FIR{
users{
uid{
username: UserName
Posts{
170802120618{
First Name: first
}
}
}
}
}
Right. You want the child key to be an autogenerated hashvalue. You can create these by using childByAutoId(). Also if I were you, I would just store that dates as string and parse those as needed. Something below would be an example:
Posts {
-Kebfdajksthm {
first_name: "first",
post_date: "yymmddhhmmss"
}
}
Try This
var post = [String]()
ref.observe(.value, with: { (snapshot) in
for item in snapshot.children{
self.post.append((item as AnyObject).key)
}
})
Then you print "post" and you will get ["170802120618", "170802101427"]
I need to make multiple observations, but I don't know how.
Here is my database structure:
"Posts" : {
"f934f8j3f8" : {
"data" : "",
"date" : "",
"userid" : ""
}
},
"Users" : {
"BusWttqaf9bWP224EQ6lOEJezLO2" : {
"Country" : "",
"DOB" : "",
"Posts" : {
"f934f8j3f8" : true
},
"Profilepic" : "",
"name" : "",
"phonenumber" : ""
}
I want to observe the posts and I write the code and it works great, but I also want to get the name of the user who posted this post but when I wrote save the name and use it it gives me null. Here is my code.
DataServices.ds.REF_POSTS.queryOrderedByKey().observe(.value,
with: { (snapshot) in
self.posts = []
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
DataServices.ds.REF_USERS.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let postusername = value?["name"] as? String ?? ""
})
print(" ------ User name : \(postusername) ------")
})
print(" ------ User name 2 : \(postusername) ------")
let post = Posts(postKey: key, postData: postsDict)
self.posts.append(post)
The first print statement prints the username, but the second one prints nothing.
Thanks in advance.
Firebase is asynchronous so you can't operate on a variable until Firebase populates it within it's closure. Additionally code is faster than the internet so any statements following a closure will occur before the statements within the closure.
The flow would be as follows
Query for the post {
get the user id from the post inside this closure
query for the user info {
create the post inside this second closure
append the data to the array inside this second closure
reload tableview etc inside this second closure
}
}
Something like this edited code
self.posts = []
myPostsRef.queryOrderedByKey().observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
myUsersRef.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userName = value?["name"] as? String ?? ""
let post = Posts(postKey: key, postData: postsDict, name:userName)
self.posts.append(post)
})
}
}
}
})
You're not using the postusername inside the closure so I added that to the Posts initialization.
Also, the self.posts = [] is going to reset the posts array any time there's a change in the posts node - you may want to consider loading the array first, and then watch for adds, changes, or deletes and just update the posts array with single changes instead of reloading the entire array each time.
Edit:
A comment was made about the data not being available outside the loop. Here is a very simplified and tested version. Clicking button one populates the array from Firebase with a series of strings, clicking button 2 prints the array.
var posts = [String]()
func doButton1Action() {
let postsRef = ref.child("posts")
self.posts = []
postsRef.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let value = snap.value as! String
self.posts.append(value)
}
}
})
}
func doButton2Action() {
print(posts)
}
I have a "flatened" Firebase structure and trying to retrieve a dictionary of values from a "secondary" database member. In other words, I have a conversation which has a "to" cell which has the key to a business listing. With this key, I'm trying to retrieve the business listing and its children (url, description, title). For some reason, I can print the snapshot2.value and it responds with the expected value, but I can't pass it to my class initialization.
DataService.ds.REF_CONVOS.observeEventType(.Value, withBlock: {snapshot in
self.convoListings.removeAll()
self.convoListings = []
//Data parsing from Firebase. The goal is to breakdown the data received and store in a local model.
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
for convo in userConvos {
// Going into the children of the main object for the conversations.
//print("\(snap)")
if convo == snap.key {
//print(snap.value)
print(snap.value)
if let businessDict = snap.value as? Dictionary<String, AnyObject> {
businessName.removeAll()
let test = businessDict["to"] as? String
DataService.ds.REF_BusinessListing.childByAppendingPath(test).childByAppendingPath("title/").observeSingleEventOfType(.Value, withBlock: { snapshot2 in
print(snapshot2.value)
})
let key = snap.key
let post = ConvoListing(convoKey: key, dictionary: businessDict, businessName: self.test2)
self.convoListings.append(post)
}
}
}
}
}
self.tableView.reloadData()
})
Your nesting seems of:
DataService.ds.REF_BusinessListing.childByAppendingPath(test).childByAppendingPath("title/").observeSingleEventOfType(.Value, withBlock: { snapshot2 in
print(snapshot2.value)
})
let key = snap.key
let post = ConvoListing(convoKey: key, dictionary: businessDict, businessName: self.test2)
self.convoListings.append(post)
Keep in mind the observeSingleEventOfType loads the data asynchronously. For this reason, if you have code that needs the value that you loaded, you need to put that code in the block:
DataService.ds.REF_BusinessListing.childByAppendingPath(test).childByAppendingPath("title/").observeSingleEventOfType(.Value, withBlock: { snapshot2 in
print(snapshot2.value)
let key = snap.key
let post = ConvoListing(convoKey: key, dictionary: businessDict, businessName: self.test2)
self.convoListings.append(post)
})