0

Im trying to query and update data to firebase. But I cannot get the data. It seems the program doesn't run into ref.observeSingleEvent what should I do to fix this?

    func getId(path:String, value:String?, completion: @escaping (String?) -> Void) {

        let ref = Database.database().reference(withPath: path).queryOrdered(byChild: "name").queryEqual(toValue: value)

        ref.observeSingleEvent(of: .childAdded, with: { snapshot in
            print("running in getId")
            completion(snapshot.key)                

        }) { (error) in
            print(error.localizedDescription)
        }
    }
@objc func onTapConfirmed(){

     self.getId(path: "patients", value: self.kardex?.patient?.name) { snapshotKey in
      BookingViewController.kardexDict["patient"] = snapshotKey as AnyObject?

      print("1:get patient name")
      }

     self.getId(path: "nurses", value: BookingTreatmentListController.nurseName) { snapshotKey in
      BookingViewController.kardexDict["nurse"] = snapshotKey as AnyObject?
      print("2:get nurse name")
      }
      Database.database().reference().child("kardexes").child(BookingViewController.newKardex.id).updateChildValues(BookingViewController.kardexDict)

     print("save kardexDict")

 }

I expect to get the following result

"1:get patient name" -> "running in getId" -> "2:get nurse name" -> "running in getId" -> "save kardexDict"

But I got
"1:get patient name" -> "2:get nurse name" -> "save kardexDict"

and the data in kardexDict is not correct since the data is not obtained from function getId() What can I do to force the program follow my order I expected.

Alex Chen
  • 21
  • 6
  • 1
    It's much better practice to *force yourself to follow the order of the program* – vadian Jul 31 '19 at 09:14
  • @vadian thanks for the reply, yes, i can follow the order of the program. But i dont know how to wait for observeSingleEvent to be finished. Do you have any suggestion? – Alex Chen Jul 31 '19 at 09:47
  • Please see https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function. The API is different but the behavior is exactly the same – vadian Jul 31 '19 at 09:49

1 Answers1

0

You are using firebase in the wrong way:

  • observeSingleEvent runs asynchronously, so the order of events will be: "running in getId" -> "1:get patient name"
  • updateChildValues will typically called before any of the observeSingleEvent finishes

Hence, you first write the empty(?) kardexDict to firebase, before you even get the patient or nurse name.

Also, be aware the the completion closure does not run in the main thread. Therefore, you will likely observe some unpredictable concurrency issues / race conditions. What you have to do is, to wait for all observeSingleEvent to be finished, and then write the data back.


Update

So, an more or less 1-to-1 transfer of your idea could look like this (might not exactly compile, because I don't have your environment running):

func getId(path:String, value:String?, completion: @escaping (String?) -> Void) {

    let ref = Database.database().reference(withPath: path).queryOrdered(byChild: "name").queryEqual(toValue: value)

    ref.observeSingleEvent(of: .childAdded, with: { snapshot in
        print("running in getId")
        completion(snapshot.key)                

    }) { (error) in
        print(error.localizedDescription)
    }
}

@objc func onTapConfirmed(){

    let patientName = self.kardex?.patient?.name
    let nurseName = BookingTreatmentListController.nurseName
    self.getId(path: "patients", value: patientName) { snapshotKey in
        print("1:got patient")
        let patient = snapshotKey as AnyObject?
        self.getId(path: "nurses", value: nurseName) { snapshotKey in
            print("2:got nurse")
            let nurse = snapshotKey as AnyObject?
            DispatchQueue.Main.async {
                print("save kardexDict (in main thread!")
                BookingViewController.kardexDict["patient"] = patient
                BookingViewController.kardexDict["nurses"] = nurse
                let kardexes = Database.database().reference().child("kardexes")
                kardexes.child(BookingViewController.newKardex.id).updateChildValues(BookingViewController.kardexDict)
            }
        }
    }
}

Nevertheless, it might not be that efficient, because there are two separate web calls involved (first the patients, then the nurses). Maybe you better use observe instead of observeSingleEvent.

Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34
  • Thanks for your help, but how can I wait. After print("save kardexDict"), the function onTapConfirmed() is finished. So I dont know how to wait for all observeSingleEvent to be finished. – Alex Chen Jul 31 '19 at 09:25