Firebase observeSingleEvent completion handler - ios

I have a FirebaseManager class and a get data function.
When calling this function, I need to get this data in the place from which it is called.
I am trying to do something like
import Firebase
class FirebaseManager {
var ref = Database.database().reference()
func getData (path: String) -> DataSnapshot {
self.ref.child(path).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
print(snapshot.value!)
return snapshot // XCODE ERROR - Cannot convert return expression of type 'Void' to return type 'DataSnapshot'
} else {
print("No data by path \(path)")
}
}
}
}

As your title suggests, what you'll need a completion handler. You can pass that in as a parameter to your function. It might look something like this:
func getData(path: String, completion: #escaping (DataSnapshot) -> Void) {
self.ref.child(path).observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
completion(snapshot)
} else {
print("No data by path \(path)")
}
}
}
Then, you can call it like this:
getData(path: "myPath") { snapshot in
//do something with snapshot here
}
//no access to snapshot here
Note that you have access to snapshot inside the closure (the { }), so you can't access snapshot after the closure.
Personally, I'd probably refactor a bit so that you'd be returning the actual data than you want from inside the snapshot (like a String, a Dictionary, etc) rather than returning the DataSnapshot itself, but that's a matter of preference.

Related

Swift : Firebase query never goes in call

Im trying to access to my firebase db with Swift
import Foundation
import FirebaseDatabase
import FirebaseAuth
import Firebase
struct FirebaseManager {
private let reference = Database.database().reference()
func fetch (_ child: String, completion: #escaping (DataSnapshot) -> Void) {
print("before reference")
reference
.child(child)
.observeSingleEvent(of: .value) { (snapdata) in
print("inside reference")
guard let response = snapdata.value else { return print("error snapdata") }
print(response)
completion(snapdata)
}
print("after reference")
}
}
To check if code is running correctly, I put some prints and this is what I see in Xcode console:
before reference
after reference
There's neither "inside reference" nor response value nor "error snapdata"
So I've deduced it never goes inside it ! And I checked: my firebase db is not empty and corresponds to child parameter string.
I don't understand where is the problem.
Same problem here : iOS Swift - Firebase query never gets called
EDIT: I also tried this:
.observeSingleEvent(of: .value, with: { (snapshot) in
print("observe single event")
if snapshot.exists() {
if let value = snapshot.value as? String {
print("inside if let", value)
}
} else {
print("else")
// username doesn't exist
}
}, withCancel:{ error in
print("cancel", error)
})
And the ouput in console is the same. Nothing appears
And this is a screenshot in rules:
You might want to use observeSingleEvent(of:with:withCancel:) instead, to see if the query gets cancelled by the database. If it does, the user does not have permission to read the data, and you should read the documentation on security rules.

Firebase query not returning any data

My datamodel looks as follows :
allcomments
|__$comment_id_5
|__post_id: <post_id_5>
uid
|
|__activity
|__comments
|__$random_activity_id
|__post_id : <post_id_5> //ref to post_id_5 in allcomments
|__comment_id : <comment_id_5> // ref to comment_id_5 in allcomments
My Goal: To check if the user with uid has commented on the post or not. If that person has, then I he can proceed further else he'll be shown something else on the screen. On trying the following query, I am able to only get the callback when a snapshot exists and not otherwise.
FBDataservice.ds.child("allcomments").queryOrdered(byChild: "post_id").queryEqual(toValue: "post_id_5").observeSingleEvent(of: .ChildAdded) { (snapshot) in
if let data = snapshot.value as? DataDict {
let comment = Comment(comId: snapshot.key , comData: data)
self.checkUserHasResponded(completion: { (hasResponded) in
if !hasResponded {
// Never returns it there is nothng
print("You gotta respond first")
} else {
//this part does work
print("Welcome to seeing everything")
}
})
}
}
func checkUserHasResponded(completion: #escaping (Bool) -> ()) {
FBDataservice.ds.REF_USERS.child(uid).child("activity/comments").queryOrdered(byChild: "post_id").queryEqual(toValue: "post_id_5").observeSingleEvent(of: .value) { (snapshot) in
snapshot.exists() ? completion(true) : completion(false)
}
}
I even tried tweaking the architecture this way and query it differently, still nothing work and the program behaves in the same exact way as incase of above.
uid
|
|__activity
|__comments
|__post_id_5 : comment_id_5
and ran this query:
func checkUserHasResponded(completion: #escaping (Bool) -> ()) {
FBDataservice.ds.REF_USERS.child(uid).child("activity/comments").observeSingleEvent(of: .value) { (snapshot) in
snapshot.hasChild("post_id_5") ? completion(true) : completion(false)
}
}
I tried changing .childAdded to .value. It gives the same exact result. Tried changing .observeSingleEvent(of:) to .observe() as well. But nothing helps. I am not sure what exactly is wrong. Check plenty of answers here, none helped. What exactly am I over looking. Thanks for the help.
Use .value instead of .childAdded, that way it the closure is called whether or not the snapshot exists, Just a quick test shows it works.
func checkUserHasResponded() {
let uid = "uid_0"
let commentsRef = dbRef.child(uid).child("activity").child("comments")
commentsRef.queryOrdered(byChild: "post_id")
.queryEqual(toValue: "post_5")
.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
print("post exists")
} else {
print("post not found")
}
}
}
If your structure does not contain a post_id child value that exists then the output is
post not found
So this answer applies to the updated question. The code in the closure will not run if the node you're querying for does not exist because the query is using .childAdded
FBDataservice.ds.child("allcomments").queryOrdered(byChild: "post_id")
.queryEqual(toValue: "post_id_5")
.observeSingleEvent(of: .childAdded) { (snapshot) in
If that's changed to .value, it returns and the code in the closure runs if the node exists. Keeping in mind that you'll want to use
snapshot.exists()
with that as it will be nil if it doesn't.

Returning data from function in Firebase observer code block swift

I'm new to firebase and I want to know if is any possible way to return data in observer block. I have class ApiManager:NSObject and in this class I want to create all my firebase function that will return some kind of data from database. This is one of my function in this class
func downloadDailyQuote() -> [String:String] {
let reference = Database.database().reference().child("daily")
reference.observeSingleEvent(of: .value) { (snap) in
return snap.value as! [String:String] //I want to return this
}
return ["":""] //I don't want to return this
}
And if I now do something like let value = ApiManager().downloadDailyQuote(), value contains empty dictionary. Is any solution for that?
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(["" : ""])
}
}
}

Add a completion to a function when firebase has finished, iOS, Swift

I'm trying to find out the best way to handle a completion on a function.
The function calls for data from firebase and adds them to an array of dictionaries. Because this is for maps and adding annotations the loop is adding lots of data before coming to the final appended version so its throwing loads of annotations dow in the same place. i want to know if i can call a completion on the loop when its finished and then call the function ShowSightings().
func getDatafromFB() {
DataService.ds.REF_POSTS.child("postCodes").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let postsIds = value?.allKeys as! [String]
for postId in postsIds {
let refToPost = Database.database().reference(withPath: "posts/" + "postCodes/" + postId)
refToPost.observe(.value, with: { snapshot in
if snapshot.exists() {
let postDict = snapshot.value as? [String: AnyObject]
print("Tony: before append post \(self.posts)")
self.posts.append(postDict!)
print("Tony: post \(self.posts)")
}else {
print("Tony: Couldn't get the data")
}
})
}
print("Tony: The compleetion result \(self.posts)")
})
}
You can try this:
func doAsyncTask(completionHandler:#escaping (Bool) -> ()){
//do async tasks
completionHandler(true) //<- call this when the data is retrieved
//so in your case, see below
}
override func viewDidLoad{
doAsyncTask(){ succes in
//succes gives true or false
}
}
//your case
}else {
print("Tony: Couldn't get the data")
}
completionHandler(true) //<- right there
This is for 1 async task. I see you want to use multiple async task. This is a job for dispatch groups. I change some of my function to take parameters. Check this out:
func doAsyncTask(postID: String, completionHandler:#escaping (Bool) -> ()){
//do async tasks
completionHandler(true)
}
override func viewDidLoad{
var arrPostIDs = [String]()
//append to arrPostIDs here
let postIDDispatchGroup = DispatchGroup()
for postID in arrPostIDs{
postIDDispatchGroup.enter()
doAsyncTask(postID: postID){ succes in
//succes gives true or false
postIDDispatchGroup.leave()
}
}
postIDDispatchGroup.notify(queue: .main) {
//everything completed :), do whatever you want
}
}

Assign a variable outside a string function in Swift 3

I am struggling for a while to assign the value returned by this function to a variable outside of it, in viewDidLoad but is returning an empty string.
getUid() - is returning the firebase uid
prRef - is calling the firebase table for users
Can someone tell me what am I doing wrong?
Thanks in advance,
var currentWorkplaceId: String?
func getCurrentWorkplaceId() -> String {
//completion:#escaping (Bool)->Void
var workplaceid = String()
prRef.child(getUid())
.child("workplace_id")
.observeSingleEvent(of: .value, with: { snapshot in
workplaceid = snapshot.value as! String
})
return workplaceid
}
usage:
currentWorkplaceId = getCurrentWorkplaceId()
observeSingleEvent is probably asynchronous with a callback. So at the time you call return, workplaceid hasn't been assigned yet. You need to do the same with your own callback.
func getCurrentWorkplaceId(_ completion: #escaping (_ workplaceId: String)->()) {
//completion:#escaping (Bool)->Void
prRef.child(getUid())
.child("workplace_id")
.observeSingleEvent(of: .value, with: { snapshot in
completion(snapshot.value as! String)
})
}
Usage:
getCurrentWorkplaceId() { workplaceId in
self.currentWorkplaceId = workplaceId
}

Resources