How can I wait data from firebase? - swift

I need to wait data from firebase otherwise my app gonna crash because of nil value. I searched about that and watch some videos and look at other questions but I can't see any firebase example actually. How can I wait data from firebase? How can I use dispatch or semaphore and which one is more efficient?
Here my sample code:
let uid = (Auth.auth().currentUser?.uid)!
let usrIndirimRef = Database.database().reference().child("users").child(uid).child("indirimSayisi")
usrIndirimRef.observe(.value) { (snap) in
let userIndir = snap.value as! Int
self.userIndirimSayisi = userIndir
let usrIndirimTipiRef = Database.database().reference().child("users").child(uid).child("indirimTipi")
usrIndirimTipiRef.observe(.value) { (snapshot) in
if userIndir < 1 {
self.userIndirim = "0"
self.indirimKontrol = false
self.tableView.reloadSections([1], with: .fade)
}else{
self.userIndirim = snapshot.value! as! String
self.tableView.reloadSections([1], with: .fade)
}
}
}
toplamHesapTutari()
Here toplamHesapTutari() function execute before the data userIndirim. Its can be solve maybe hold execution for that function but I can't decide that is correct way.

Don't wait, never wait, run the code you need to run inside the completion handler
let uid = (Auth.auth().currentUser?.uid)!
let usrIndirimRef = Database.database().reference().child("users").child(uid).child("indirimSayisi")
usrIndirimRef.observe(.value) { (snap) in
let userIndir = snap.value as! Int
self.userIndirimSayisi = userIndir
let usrIndirimTipiRef = Database.database().reference().child("users").child(uid).child("indirimTipi")
usrIndirimTipiRef.observe(.value) { (snapshot) in
if userIndir < 1 {
self.userIndirim = "0"
self.indirimKontrol = false
self.tableView.reloadSections([1], with: .fade)
}else{
self.userIndirim = snapshot.value! as! String
self.tableView.reloadSections([1], with: .fade)
}
self.toplamHesapTutari()
}
}

I had this problem before and I tried a lot of methods but still crash my app.
So I figure it out by putting the code that makes my app crash in a later function to spare some time for the essential data fully loads. I suggest you use try-catch and log to observe the error and prevent your app from keep crashing.
try {
if ((snapshot.child("photo").value as? String)==null) {
//if not load
} else {
//if load
}
}catch(ex: Exception){}
Here is some reference for you:
https://kotlinlang.org/docs/reference/exceptions.html

Related

Item keeps getting added to Tableview even if I just have one in my DB

I have this code and I call it every time I click on a button and call another viewcontroller where I call fetchVinylData inside viewWillappear. The problem is everytime I click a button to go to this VC it adds one item to the tableview even if I only have one inside my database.I am guessing is because my array keeps getting fed even if there is only one record in my database.How do I delete from my array so I don't get many values inside my tableView only the ones that is saved on my firebase database?I tried to add myArray.removeAll() inside fetchrequest before I load the vinyl to the array but my app crashs eventually
func fetchVinylData() {
SVProgressHUD.show()
guard let currentUID = Auth.auth().currentUser?.uid else { return }
dbRef.child("vinylsOUT").child(currentUID).observe(.childAdded) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String,AnyObject> else { return }
let vinyl = Vinyl(dictionary: dictionary)
self.vinyls.append(vinyl)
self.vinyls.sort(by: { (vinyl1, vinyl2) -> Bool in
return vinyl1.artist < vinyl2.artist
})
self.tableView.reloadData()
}
SVProgressHUD.dismiss()
}
thank yo very much
You are appending to your array every time you get back data from Firebase:
- self.vinyls.append(vinyl)
You can just override your current vinyls array by doing:
self.vinyls = [Vinyl]()
before you fetch the new data from Firebase.
That would look like this:
func fetchVinylData() {
SVProgressHUD.show()
guard let currentUID = Auth.auth().currentUser?.uid else { return }
self.vinyls = [Vinyl]() // <- here you reset the array
dbRef.child("vinylsOUT").child(currentUID).observe(.childAdded) { (snapshot) in
guard let dictionary = snapshot.value as? Dictionary<String,AnyObject> else { return }
let vinyl = Vinyl(dictionary: dictionary)
self.vinyls.append(vinyl)
self.vinyls.sort(by: { (vinyl1, vinyl2) -> Bool in
return vinyl1.artist < vinyl2.artist
})
self.tableView.reloadData()
}
SVProgressHUD.dismiss()
}

Firebase don't send me my value into my variable

I've got a code which normally should return to me a value from Firebase.
My Firebase struct is :
Experience{
UserId{
LDG_DAY: "4"
LDG_NIGHT: "0"
APCH_IFR: "0"
}
}
My code is :
func getUserExp(){
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) {(snapshot) in
if snapshot.hasChild(userID!){
let value = snapshot.value as? NSDictionary
let ldg_day = value?["LDG_DAY"] as? String ?? "123"
let ldg_night = value?["LDG_NIGHT"] as? String ?? "0"
let apch_ifr = value?["APCH_IFR"] as? String ?? "0"
self.intLdgDay = Int(ldg_day)!
self.intLdgNight = Int(ldg_night)!
self.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
}
}
}
Now the code didn't work as I would like... In fact my code return the basic as? String ?? "123" value but the snapshot.value get the good value from firebase ...
What's wrong ? I use this code for many other part of my app and no problems about it ?
Thanks for your help
I believe you want to ensure the node exists before trying to read the child data.
NOTE:
I see the path to read has the uid commented out so it's unclear if you intended to read a single user (leaving in the uid) or if you actually wanted to load every user at one time (thousands). This answer assumes you are intending to read that specific user node only. See #Callam answer if you intended to read ALL of the users nodes at one time.
The code you have now is using snapshot.hasChild which looks within the node to see if the child, the users uid exists, and it doesn't so the code will always fail.
if snapshot.hasChild(userID!)
I think what you want to do is use snapshot.exists to ensure it's a valid node before reading. Here's the code:
let experienceRef = self.ref.child("Experience")
let usersExpRef = experienceRef.child(uid)
usersExpRef.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as! [String: Any]
let ldg_day = value["LDG_DAY"] as? String ?? "123"
print("string = \(ldg_day)")
} else {
print("the \(uid) node does not exist")
}
}
I would also suggest safely unwrapping options before attempting to work with them as they could be nil, and that would crash your code.
guard let thisUser = Auth.auth().currentUser else { return }
let uid = thisUser.uid
Note I also replaced the old objc NSDictionary with it's Swifty counterpart [String: Any]
Assuming your struct is from the root, and Experience contains more than one user ID, your code is currently observing the value for all user IDs since the /*.child(userID!)*/ is commented out.
Therefore you are requesting every user's experience and checking on the client if the current user exists as a child – this will succeed if the current user's ID is present at Experience/$uid.
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.hasChild(userID!) {
let value = snapshot.value as? NSDictionary
Now we have a snapshot with all Experiences and we've confirmed that it has a child for the current user's ID – we would need to get that child and cast the value of that to a dictionary.
let value = snapshot.childSnapshot(forPath: userID).value as? NSDictionary
This fixes the issue but obviously, we don't want to download every experience on a single user's device, and they maybe shouldn't even have the permission to request that reference location either.
So if you uncomment .child(userID!), the snapshot will be of just one Experience, so snapshot.hasChild(userID!) will fail. Instead, you can use snapshot.exists() and/or a conditional cast to determine if the snapshot for the userID is existent and/or thereby castable.
func getUserExp() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as? [String:String]
let ldg_day = value?["LDG_DAY"] ?? "123"
let ldg_night = value?["LDG_NIGHT"] ?? "0"
let apch_ifr = value?["APCH_IFR"] ?? "0"
self?.intLdgDay = Int(ldg_day)!
self?.intLdgNight = Int(ldg_night)!
self?.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \(snapshot.key) doesn't exist")
}
}
}
You can clean this up a bit with a struct and extension.
// Experience.swift
struct Experience {
var ldg_day: String
var ldg_night: String
var apch_ifr: String
}
extension Experience {
static var currentUserRef: DatabaseReference? {
return Auth.auth().currentUser.flatMap {
return Database.database().reference(withPath: "Experience/\($0.uid)")
}
}
init?(snapshot: DataSnapshot) {
guard snapshot.exists() else { return nil }
let value = snapshot.value as? [String:String]
self.ldg_day = value?["LDG_DAY"] ?? "123"
self.ldg_night = value?["LDG_NIGHT"] ?? "0"
self.apch_ifr = value?["APCH_IFR"] ?? "0"
}
}
Et voilà,
func getUserExp() {
Experience.currentUserRef?.observeSingleEvent(of: .value, with: { [weak self] in
if let experience = Experience(snapshot: $0) {
self?.intLdgDay = Int(experience.ldg_day)!
self?.intLdgNight = Int(experience.ldg_night)!
self?.intApchIfr = Int(experience.apch_ifr)!
print("string = \(experience.ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \($0.key) doesn't exist")
}
})
}

Issues when trying to remove the firebase reference

I am having some issues while trying to delete one of the tableView row - in my case I was trying to delete the data from Firebase and then reload the table view.
See the function below:
func deleteMeds() {
Database.database().reference().child("Meds_Database").child("UsersID").child((Auth.auth().currentUser?.uid)!).child("User_Medications").observe(DataEventType.childRemoved, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let medication = Meds()
medication.medName = (dictionary["Medication_Name"]) as! String
medication.medDosage = (dictionary["Medication_Dosage"]) as! String
medication.medEdit = (dictionary["Medication_Frequency"]) as! String
medication.medAlarm = (dictionary["Medication_Reminder"]) as! String
self.meds.remove(at: 0)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
print(snapshot)
}, withCancel: nil)
}
I think I am getting confused with the Firebase Syntax... Can somebody help me? The database is like this:
I am sure someone can help
You only need to delete the relavant object not always index 0 here
self.meds.remove(at: 0)
So replace it with
self.meds = self.meds.filter{ $0 != medication }
and adopt the Equatable protocol

Firebase one of two observers not working

I have two observers, the second observer is dependent on the first observers value. I can't seem to get the first observer to work, I am not getting any errors on Xcode. The first function has to check the Users profile for information and then use that information to search for different information in the database. Here is my code:
func loadposts() {
ref = Database.database().reference()
let trace = Performance.startTrace(name: "test trace")
trace?.incrementCounter(named:"retry")
let userID = Auth.auth().currentUser?.uid
print(userID!)
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let one1 = value?["Coupon Book"] as? String ?? ""
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
}) { (error) in
print(error.localizedDescription)
}
delay(0.1) {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
Database.database().reference().child("Coupons").child(self.bogus.string(forKey: "bogus")!).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] as! String
let offerx = dict["offer count"] as! String
let logocomp = dict["logo"] as! String
let actchild = dict["childx"] as! String
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
self.tableview.reloadData()
print(self.posts)
}
}
}
trace?.stop()
}
Any help is appreciated.
self.bogus.string(forKey: "bogus"))" is nil because observeSingleEvent is an async method, so to get the required results you need to call the second observer inside the first observer or you can use the completion handler
You can use the completionHandler like this:
guard let uid = Auth.auth().currentUser?.uid else {
return
}
func firstObserverMethod(completionCallback: #escaping () -> Void) {
ref.child("Users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let value = snapshot.value as? [String: Any] {
let one1 = value["Coupon Book"] as? String
print("one1: \(one1)")
self.bogus.set(one1, forKey: "bogus")
completionCallback()
}
}) { (error) in
print(error.localizedDescription)
}
}
Now using the above method:
firstObserverMethod {
print("bogus: \(self.bogus.string(forKey: "bogus"))")
guard let bogusString = self.bogus.string(forKey: "bogus") else {
print("bogus is not set properly")
return
}
Database.database().reference().child("Coupons").child(bogusString).observe(.childAdded) { (Snapshot : DataSnapshot) in
if let dict = Snapshot.value as? [String: Any] {
let captiontext = dict["company name"] ?? ""
let offerx = dict["offer count"] ?? ""
let logocomp = dict["logo"] ?? ""
let actchild = dict["childx"] ?? ""
let post = Post(captiontext: captiontext, PhotUrlString: actchild, offertext: offerx, actualphoto: logocomp)
self.posts.append(post)
DispatchQueue.main.async {
self.tableview.reloadData()
}
print(self.posts)
}
}
}
Note: You should use optional binding to get the values from optional
Since you are using the result of the 1st observer in the reference of your 2nd observer, it's a very bad idea to add the 2nd observer right below the first observer. And adding a delay won't be a viable solution : these two calls are asynchronous, which means that the reason why you are not getting might very likely be because the 2nd observer is triggered even before the 1st has returned any data.
The solution here, would be using a completion handler, or you could just incorporate your 2nd observer inside the completion block of the 1st, to be make sure that the proper order (1st observer -> 2nd observer) will always be respected.
It would look somehow like this:
func loadposts() {
// ...
// 1st Observer here
ref.child("Users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get your value here
guard let one1 = snapshot.childSnapshot(forPath: "Coupon Book").value as? String else { return }
// 2nd Observer here. Now you can use one1 safely:
Database.database().reference().child("Coupons").child(one1).observe(.childAdded) { (Snapshot : DataSnapshot) in
// ...
}
})
}
Now, a couple of things that you could also improve in your code, while not directly related to the question:
I would suggest you to make use of guard statements instead force-unwrapping, which may end up in crashing your app at some point.
For example, you could check whether your current user exist or not like so:
guard let currentUserID = Auth.auth().currentUser?.uid else {
return
}
// Now you can use safely currentUserID
Also, when you try to get the data out of the snapshot, it's not a good idea either, to use force-casting. You would better write it in this way:
yourRef.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let text = child.childSnapshot(forPath: "text").value as? String, let somethingElse = child.childSnapshot(forPath: "otherValue").value as? NSNumber else {
return
}
// And so on, depending of course on what you have in your database.
}

IOS - Parsing objects from Firebase - Code running before download is completed

I am trying to parse data from Firebase into an array of objects, and upon completion display the text from the first object in the array. However, I can't work out/find a solution to stop the code continuing before the download is complete. So it proceeds to update the user's completion to true, without displaying the text. This is the function as is, the downloading and appending to array works fine, but it skips to displayNextInSeries() before it's finished...
func parseSeries (ref: String) {
FIRDatabase.database().reference().child("library").child("series").child(ref).observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print(snap)
if let pushSeriesDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let push = PUSH_SERIES(pushKey: key, pushSeriesData: pushSeriesDict)
self.seriesArray.append(push)
}
}
}
})
print("\(ref) Series Count: \(self.seriesArray.count)")
displayNextInSeries()
}
The display next in series function sees the seriesArray.count = 0, so it doesn't update the texLbl:
func displayNextInSeries() {
if seriesProgress < seriesArray.count {
animateProgress(current: seriesProgress, total: seriesArray.count)
currentPUSH_SERIES = seriesArray[seriesProgress]
currentPUSH_SERIES.text = personaliseText(text: currentPUSH_SERIES.text)
textLbl.animateUpdate(currentPUSH_SERIES.text, oldText: previousText)
titleLbl.text = "\(currentPUSH_SERIES.title!)"
previousText = currentPUSH_SERIES.text
seriesProgress += 1
} else {
animateProgress(current: sessionProgress, total: sessionTarget)
titleLbl.text = ""
greetingPush()
seriesPlay = false
seriesProgress = 0
user.updateProgress(seriesName)
print(user.progress)
}
}
I may be doing something fundamentally wrong here. Your help is much needed and much appreciated! Thanks, Matt
The observeSingleOfEvent is an asynchronous call, calling the function inside the completionBlock will solve it,The problem is that your print function is being called even before observeSingleOfEvent is finished downloading data :-
func parseSeries (ref: String) {
FIRDatabase.database().reference().child("library").child("series").child(ref).observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print(snap)
if let pushSeriesDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let push = PUSH_SERIES(pushKey: key, pushSeriesData: pushSeriesDict)
self.seriesArray.append(push)
print("\(ref) Series Count: \(self.seriesArray.count)")
displayNextInSeries()
}
}
}
})
}