Returning data from function in Firebase observer code block swift
Update: When you call .observeSingleEvent, you call the method asynchronously. This means that the method will start working, but the response will come later and will not block the main thread. You invoke this method, but there is no data yet and therefore you return an empty dictionary.
If you use the completion block, then you will get the data as soon as the method action is completed.
func downloadDailyQuote(completion: @escaping ([String:String]) -> Void) {
let reference = Database.database().reference().child("daily")
reference.observeSingleEvent(of: .value) { (snap) in
if let dictionaryWithData = snap.value as? [String:String] {
completion(dictionaryWithData)
} else {
completion(["" : ""])
}
}
}
Firebase Observe called after following command
Firebase loads data from its database asynchronously. This means that the code executes in a different order from what you may expect. The easiest way to see this is with some log statements:
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
print("Before attaching observer")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
print("In completion handler")
})
print("After attaching observer")
Unlike what you may expect, this code prints:
Before attaching observer
After attaching observer
In completion handler
This happens because loading data from Firebase (or any other web service) may take some time. If the code would wait, it would be keeping your user from interacting with your application. So instead, Firebase loads the data in the background, and lets your code continue. Then when the data is available, it calls your completion handler.
The easiest way to get used to this paradigm is to reframe your problems. Instead of saying "first load the data, then add it to the list", frame your problem as "start loading the data. when the data is loaded, add it to the list".
In code this means that you move any code that needs the data into the completion handler:
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.username = snapshot.value as! String
let nameModel = NameModel(name: self.username, uid: *some UID*)
decoratedItems.append(DecoratedChatItem(chatItem: nameModel, decorationAttributes: nil))
})
For some more questions on this topic, see:
- Swift: Wait for Firebase to load before return a function
- Can Swift return value from an async Void-returning block?
- Array items are deleted after exiting 'while' loop?
- Asynchronous functions and Firebase with Swift 3
Firebase observe method won't return and continue
The call to Firebase is asynchronous, so you have to use completion in your function to get the data. Try something like this:
func getEmail(name: String, completion: @escaping (Bool, Any?, Error?) -> Void) {
var email = ""
ref = Database.database().reference()
self.ref.child("Users").child(name).observeSingleEvent(of: .value, with: { (snapshot) in
if let user = snapshot.value as? [String:Any] {
email = user["email"] as! String
completion(true, email, nil)
}
else {
completion(false, nil, nil)
}
}){ (error) in
completion(false, nil, error)
}
}
func validate(){
if(Username.text == ""){
EmptyStringAlert();
}
getEmail(name: Username.text!) { (success, response, error) in
guard success, let email = response as? String else {
print(error ?? "Failed getEmail..")
return
}
if(email == "") {
return
}
performSegue(withIdentifier: "LoginSuccess", sender: nil)
}
}
Wait for Firebase to load before returning from a function
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function downloadFileAtURL
, which takes a completion handler and does an async download:
typealias DataClosure = (Data?, Error?) -> Void
/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {
static var downloadManager = DownloadManager()
private lazy var session: URLSession = {
return URLSession.shared
}()
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: @escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
The code above uses Apple's URLSession
class to download data from a remote server asynchronously. When you create a dataTask
, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.
That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}
.
Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from @Raghav7890's answer to use a typealias:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler: @escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler:@escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}
Swift 5, how to execute code after fetching all childadded from Firebase
One option is to leverage that Firebase .value functions are called after .childAdded functions.
What this means is that .childAdded will iterate over all childNodes and then after the last childNode is read, any .value functions will be called.
Suppose we want to iterate over all users in a users node, print their name and after the last user name is printed, output a message that all users were read in.
Starting with a simple structure
users
uid_0
name: "Jim"
uid_1
name: "Spock"
uid_2
name: "Bones"
and then the code that reads the users in, one at a time, prints their name and then outputs to console when all names have been read
var initialRead = true
func readTheUsers() {
let usersRef = self.ref.child("users")
usersRef.observe(.childAdded, with: { snapshot in
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "no name"
print(userName)
if self.initialRead == false {
print("a new user was added")
}
})
usersRef.observeSingleEvent(of: .value, with: { snapshot in
print("--inital load has completed and the last user was read--")
self.initialRead = false
})
}
and the output
Jim
Spock
Bones
--inital load has completed and the last user was read--
Note this will leave an observer on the users node so if a new user is added it will print their name as well.
Note Note: self.ref points to my root firebase reference.
Swift - How to add a removed observer in firebase
Issue is resolved not by using addObserver method but by doing this.
- Created a snapshot class level of var for snapshot
var dataSnapshot: ((DataSnapshot)->Void)?
- Saved the snapshot
dataSnapshot = { (snapshot: DataSnapshot) in
....
}
- Set observer using data snapshot
firebaseRef.observe(DataEventType.value, with: dataSnapshot)
- Removed the observer
firebaseRef?.removeObserver(withHandle: refHandle)
When I needed to add the observer again I just started another observer with the same snapshot
Set observer using data snapshot
firebaseRef.observe(DataEventType.value, with: dataSnapshot)
Related Topics
Swift 3 Nscache Generic Parameter 'Keytype' Could Not Be Inferred
How to Use Multiple Protocols in Swift with Same Protocol Variables
How to Set Kerning (Spacing Between Characters) on Uinavigationbar Title - Swift or Objective-C
Apple Watch Table - First 4 Rows Not Appearing
Swift Scenekit - Centering Scntext - the Getboundingboxmin:Max Issue
Swift: Enums That Use Closures
Swift Nfc Mifare - Nfciso7816Apdu Sendmifare Command Not Supported
iOS Healthkit How to Save Heart Rate (Bpm) Values? Swift
How to Call Presentviewcontroller in Uiview Class
How to Detect Which Annotation Was Selected in Mapview
How to Use a Custom Initializer on a Uitableviewcell
Avmutablevideocomposition Output Video Shrinked
How to Change the Nstimeinterval of an Nstimer After X Seconds
Setting Custom Http Headers in Alamofire in iOS 7 Not Working
How to Use Po Command in Console (Debug Area)