Access Firebase Variable Outside Closure

Access Firebase variable outside Closure

Firebase is asyncronous and the data is only valid when it's returned from Firebase within the closure.

 FIRDatabase.database().reference(withPath: "data").child("numCells")
.observeSingleEvent(of: .value, with: { snapshot in
if let snapInt = snapshot.value as? Int {
self.navigationItem.title = String(snapInt)
}
})

Expanding from that, suppose we want to populate an array to be used as a dataSource for a tableView.

class ViewController: UIViewController {
//defined tableView or collection or some type of list
var usersArray = [String]()
var ref: FIRDatabaseReference!

func loadUsers() {
let ref = FIRDatabase.database().reference()
let usersRef = ref.child("users")

usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot {
let userDict = child as! [String: AnyObject]
let name = userDict["name"] as! string
self.usersArray.append[name]
}
self.myTableView.reloadData()
})
}
print("This will print BEFORE the tableView is populated")
}

Note that we populate the array, which is a class var, from within the closure and once that array is populated, still within the closure, we refresh the tableView.

Note that the print function will happen before the tableView is populated as that code is running synchronously and code is faster than the internet so the closure will actually occur after the print statement.

How can i access firebase variable outside firebase function

Agreed with Jay's comment. You cannot return the status like that because Firebases works asynchronously... what I would do, is add a closure parameter that executions on completion like so:

class signUpClass:UIViewController {

// check to see if form is empty

let ihelpController = UIViewController()
var CurrentStatus:status!

func signUp(var formArray: [String:String], complete:(CurrentStatus)->()){

var formStatus:status = ihelpController.checkIfFormIsEmpty(formArray)

if (formStatus == status.success){
//form is ok to process
// check DOB
//TODO: create date calculation function

let DateOfBirth:Int = 18

if DateOfBirth < 18 {
//user is not 18 they can not register
alertError("oops", message: "You must be 18 to register", comfirm: "Ok")

} else {
//Proceed with registration
let firebaseController = Firebase()
var email = "asdf@afd.com"
var password = "1234"

firebaseController.refPath("users").createUser(email, password: password, withValueCompletionBlock: {error, result in

if error != nil {
print("registration Error")
self.alertError("oops", message: "That email is registered already", comfirm: "OK")

} else {
let vc =
print("user can register")
firebaseController.firebaseRefUrl().authUser(email, password: password, withCompletionBlock:{
error, authdata in

if error != nil {

print("login Error")
}else{

let userId = firebaseController.firebaseRefUrl().authData.uid

formArray["userId"] = userId

firebaseController.refPath("users/\(userId)").updateChildValues(formArray)
print("user is register and can proceed to dashBoard")

//Send status to callback to handle
complete(status.success)
}
})
}
})
}
}
}

Why can I not modify a variable declared outside a closure?

It's because data is loaded from Firestore asynchronously, and your main code continues to run while that is happening. It's easiest to see by placing a few log statements:

let docRef = db.collection("colleges").document("UMD")
print("Before starting to get data")
docRef.getDocument { ( document, error) in
print("Got data")
}
print("After starting to get data")

When you run this code, it prints:

Before starting to get data

After starting to get data

Got data

This is probably not the order you expected, but it does completely explain why your code doesn't work. By the time your pickerData.append(docData) runs, the docData = document!.get("Name") as! String hasn't run yet.

For this reason, any code that needs data from the database needs to be inside the closure, or be called from there:

let docRef = db.collection("colleges").document("UMD")
var docData = ""
docRef.getDocument { ( document, error) in
if error == nil {
docData = document!.get("Name") as? String ?? "No Name"

pickerData.append(docData)
picker.delegate = self
picker.dataSource = self
} else{

}
}

Also see:

  • Asign value of a Firestore document to a variable
  • Using Firestore DB, how can I break out of a for loop inside of a snapshot listener when a certain condition is met?, which also shows using a custom closure to keep your own code outside of the getDocument closure.
  • Storing asynchronous Cloud Firestore query results in Swift, showing using a dispatch group to keep the code

Accessing a variable outside closure Swift3

this is what should happens because observe function is asynchronous and the rest of your code is synchronous, in addition if you remove the optional unwrap from requestPostArray? you will get a nil exception because the async task needs time to get executed so the compiler will execute the snyc task before it.
basically what you have to do is the following

 else {

ref.child("Request Posts").observe(.value, with: { (snapshot) in
let count = Int(snapshot.childrenCount)

// Request Post database is empty
if count == 0 {
cell.nameLabel.text = "No requests so far."
cell.userNameLabel.isHidden = true
cell.numberOfRequestsLabel.isHidden = true
}

// Request Post data is populated
else {

var requestPostArray: [RequestPost]? = []
self.ref.child("Request Posts").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {

for child in result {
let post = RequestPost(snapshot: child)
requestPostArray?.append(post)
}
}

else {
print("No Result")
}
print("RequestPostArray size = \(requestPostArray?.count ?? 90)")

cell.nameLabel.text = self.requestPostArray?[indexPath.row].name
cell.userNameLabel.text = self.requestPostArray?[indexPath.row].name
cell.numberOfRequestsLabel.text = self.requestPostArray?[indexPath.row].name
})



}
})
}

another advice think about using singleton so you can gain the reuse of your object and you will not invoke the database several time at the same method like you are doing now.

Access array outside of Firebase call

The observe() method is asynchronous, so after you call postsRef.observe the code executed within that closure is run ONLY AFTER the application receives a response from Firebase, so there's a delay. All code after this call that's NOT stored within the closure will be executed immediately though.

So the .observe asynchronous function call is executed, and then the next line under // HERE IT DOESN'T WORK is executed immediately after. This is why this doesn't work because test.tempPosts doesn't contain any values until after the server response is received, and by that time, your print statement outside the closure has already run.

Check out this StackOverflow answer to get some more information about async vs sync.

Asynchronous vs synchronous execution, what does it really mean?

Also too, you may want to look into closures on Swift here.

If you want to access the value outside of the closure, you'll need to look into using a completion handler or a class property.

Edit:
Here's an example

func observe (finished: @escaping ([Post]) -> Void) {
// ALL YOUR CODE...

finished(test.tempPosts)
}

func getTempPosts () {
observe( (tempPosts) in
print(tempPosts)
}
}

Can't assign a value to variable inside of closure Swift

Once it is a Async call - you cannot synchronously return the value from the function. You should accept a callback to the function that will accept the count. That callback function or closure will be passed the value asynchronously.

func getEventCount (callback: @escaping(Result<Int, Error>) -> Void) {
db.collection("Events").whereField("owner", isEqualTo: currentUser.email).getDocuments { (snapshot, error) in
if error != nil {
let result = Result.failure(error)
callback(result)
}else if let snapshot = snapshot {
let result = Result.success(snapshot.count)
callback(result)
} else {
let result = Result.failure(SomeCustomAppError)
callback(result)
}
}
}

Then you can call this function passing in a callback

self.getCount() { result in
switch result {
case .success(let count): /// use count
print(count)
// only here u can assign the count value to ur variable
case .error(let error): /// handle error
print(error.localizedDescription)
}
}

Note: In the above I've used the Result datatype from Swift standard library - https://developer.apple.com/documentation/swift/result so that both error
or result can be passed back



Related Topics



Leave a reply



Submit