How to use variables outside of .observe - swift

I currently have the code below. This successfully pulls the information from firebase and then appends it to the array of dictionaries. My question is how do I use this dictionary outside of .observe? I understand the issue of async vs sync with firebase, but I haven't found a good explanation on how I can use this data in another function.
override func viewDidLoad() {
FIRDatabase.database().reference().child("vendors").observe(.value, with: { (snapshot) in
let spot = snapshot.value as! [String : AnyObject]
for each in spot{
let name2 = each.value["name"] as! String
let booth2 = each.value["boothNum"] as! String
let desc2 = each.value["description"] as! String
self.name = name2
self.booth = booth2
self.desc = desc2
let dict1: [String: String] = ["name" : name2, "booth" : booth2, "desc" : desc2]
self.vendorDict.append(dict1)
dump(self.vendorDict)
}
})
this successfully dumps the info I need in the .observe but if I dump the dictionary outside it says it is empty.

Your best option is to call needed function at the end of the closure, but outside of the 'for' loop. This way you will have the dictionary populated, and still be on the same thread.
Other options are - adding a completion block and creating a protocol (can't elaborate more on these two since not comfortable with them myself).

Related

How to unpack multiple levels of nested JSON in Firebase Database

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

Firebase query retrieves data in a random order. The data is organised by autoID

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.

Search for sub child in firebase database

Firebase Structure
Hi, I am try to work out how to query jobBrand & jobName in my Firebase Database (Structure Attached) and store it in a tableView. I am going to store more information under each Job Brand so I would like to keep the structure this way if possible?
So far, I can get tableView to read the 2 fields if they are one level up, so the structure would be:
tbaShootApp -> Jobs -> Job Brand > data.
I cannot work out to query down another level and store it in the tableView. Is this possible?
I am using a dictionary to store the job information:
class Job: NSObject {
var id: String?
var jobBrand: String?
var jobName : String?
var director : String?
var jobInfo : jobInfo?
init(dictionary: [String: AnyObject]) {
self.id = dictionary["id"] as? String
self.jobBrand = dictionary["jobBrand"] as? String
self.jobName = dictionary["jobName"] as? String
self.director = dictionary["Director"] as? String
}
}
Here is the code to query the data - I have the function 'fetchJobs' in my superViewDidLoad.
func fetchJobs() {
Database.database().reference().child("Jobs").observe(.childAdded) { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let job = Job(dictionary: dictionary)
job.id = snapshot.key
self.jobs.append(job)
//this will crash because of background thread, use dispatch_async to fix
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}
}
JSON
{
"Jobs" : {
"Candycrush" : {
"cameraInfo" : {
"cameraBody" : "A",
"cameraBrand" : "Arri",
"cameraType" : "Alexa"
},
"jobInfo" : {
"jobBrand" : "CandyCrush",
"jobName" : "Winter"
}
},
"Honda" : {
"cameraInfo" : {
"cameraBody" : "A",
"cameraBrand" : "Arri",
"cameraType" : "Alexa XT"
},
"jobBrand" : "Honda",
"jobName" : "Comet"
},
"WWF" : {
"cameraInfo" : {
"cameraBody" : "A",
"cameraBrand" : "Red",
"cameraType" : "Epic"
},
"jobInfo" : {
"jobBrand" : "WWF",
"jobName" : "Eye"
}
}
}
}
There's a long way to go but maybe this will help:
.child("Jobs").observe(.childAdded)
It will let you know each time a new Jobs is added (Candycrush, Honda etc).
(Note - apart from anything else, you very likely also want to observe removals on that list, also.)
If you are making a table (or whatever ... some sort of list, paging thing, collection of tabs, or the like):
almost certainly each row of the table will, on it's own, want to observe that job.
So the first row, would observe for changes in Jobs/Candycrush
So the second row, would observe for changes in Jobs/Honda
And so on. Each table row (or screen, panel, bubble, tab, or whatever it is) would, on it's own, observe that thing.
Incidentally, almost certainly the "first level" header there (where you have Honda, Candycrush etc) would be an id string. (Just use a UUID, or let Firebase do it.) And then a field "Title" would be Honda etc. It would be very unusual to use the actual title as the sort of ID there. (Note that, apart from anything else, you then can't change/edit the title!).
UPDATE
Since you have added a lot more information since I posted my answer. It is now obvious that you want to query deeper. Additionally, since you posted the actual JSON it is now obvious that the JobBrand node is dynamic. You can query one level below an unknown child. You can read about that here.
I'm going to change .observe(.childAdded) to .observeSingleEvent(of: .value) because you are querying so deep I doubt you want to observe that node, but I could be wrong. Changing this will pull the data in once and only once. If you want to update that value you will have to query again.
You want a query like this:
Database.database().reference().child("Jobs").queryOrdered(b‌​yChild: "jobInfo/jobBrand").queryEqual(toValue: "JobBrandYouWant").observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let job = Job(dictionary: dictionary)
job.id = snapshot.key
self.jobs.append(job)
}
})
ORIGINAL ANSWER
You current query is going to return all of the data under jobs. Which includes the nodes Job Brand -> jobinfo -> etc.
If you are trying to get less data then you need to know either the JobBrand, jobinfo or both. However, I think you want all of the jobs and your query just isn't working.
You current query fails because your dictionary contains everything under "Jobs" not just one job. You want to loop over the data snapshot and
get each job before you call init.
Database.database().reference().child("Jobs").observe(.childAdded, with: { snapshot in
for child in snapshot.children { // over over the snapshot and cast each "job" to it's own dictionary
let child = child as? DataSnapshot
if let key = child?.key {
if let dictionary = child?.value as? [String: AnyObject] {
let job = Job(dictionary: dictionary)
job.id = key
self.jobs.append(job)
}
}
}
})
The documentation provides this example for looping over a snapshot
_commentsRef.observe(.value) { snapshot in
for child in snapshot.children {
...
}
}

Print specific child firebase db

I have the following database layout:
-PGroups
-Date
-GameTime
-Entry Cost
I know you can use the following to print out everything in PGroups
ref.child("PGroups").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
//makes sure there are public
if (snapshot.hasChildren()){
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as! FIRDataSnapshot {
print(rest.value)
}
Is there a way to just print out the "GameTime" Level children, or just the "Date" Children?
You can try making a dictionary out of the data's children, something like this might work.
ref.child("PGroups").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let snapValue = snapshot.value as [String : [String : String]]{
// Here try printing out the dictionary and seeing how to correctly get the right value out.
}
I'm a little confused about your database structure but you might have to create a dictionary one level deeper if this doesnt work, i.e. [String : [String : [String : String]]]. If you give a more detailed picture of the structure I could better help you get out the value you want.

Handling Swift Dicts: fatal error: can't unsafeBitCast between types of different sizes

I have a function in Swift that needs to be able to handle multiple types. Specifically, it needs to be able to parse both Dicts and Strings.
The problem I have is the Dicts could be several types, depending on their origin. So I could be provided with [String:Any] or [String:String] (coming from Swift) or [String:AnyObject] (coming from objc). The top level parsing function takes Any, which it then tests for specific types and attempts to parse them.
At first I just tried testing for if let dict = object as? [String:Any], but if I passed in another type [String:AnyObject] or [String:String] it failed. So I tried testing each type:
func parseLink(object: Any) {
if let dict = object as? [String:Any] {
return self.parseDict(dict)
} else if let dict = object as? [String:AnyObject] {
return self.parseDict(dict)
} else if let dict = object as? [String:String] {
return self.parseDict(dict)
} else if let string = object as? String {
return parseURL(string)
}
}
func parseDict(dict: [String:Any]) { ..... }
So I've created some Unit Tests to test the behavior:
func testDictTypes() {
let testDict: [String:Any] = [ "orgId" : "123456789" ]
let link = OrgContextLinkParser().parseLink(testDict)
XCTAssertNotNil(link)
let testDict1: [String:AnyObject] = [ "orgId" : "123456789" ]
let link2 = OrgContextLinkParser().parseLink(testDict1)
XCTAssertNotNil(link2)
let testDict3: [String:String] = [ "orgId" : "123456789" ]
let link3 = OrgContextLinkParser().parseLink(testDict3)
XCTAssertNotNil(link3)
}
This all compiles fine, but I get a fatal runtime error if a [String:AnyObject] is passed in. This is troubling since Swift's type system is supposed to prevent these kind of errors and I get no warning or errors thrown when I compile.
I also really don't want to duplicate the exact same logic multiple times just to handle different dict types. I.E., handling [String:Any], [String:AnyObject] and [String:String] have virtually the exact same logic.
The only possible solution I've seen is to actually duplicate the dictionary, which seems rather expensive (Convert [String: AnyObject] to [String: Any]). For performance reasons, it seems better to just copy paste the code and change the function signatures... but really!? That's seems excessive.
The best solution seems to be to parse a Dict as [String:AnyObject] and copy the value only if it's [String:Any]:
if let dict = object as? [String:Any] {
var objDict: [String:AnyObject] = [:]
for (key, value) in dict {
if let obj = value as? AnyObject {
objDict[key] = obj
}
}
return self.parseDict(objDict)
I don't particularly like this, but so far it's be best I've been able to come up with.
Does anyone have any idea how to handle this properly? I'm especially concerned that I can cast Any as [String:AnyObject], pass it to a function that takes [String:Any] and I get no compiler errors, even though it crashes at runtime.