For some reason when I run the following method, the first query runs after the method completes. I tried using a dispatch block in order to force the query to run first, but the query never runs at all then and the app simply freezes. Let me know if you know what is wrong.
Method without dispatch group:
func loadConversations() {
let ref = FIRDatabase.database().reference()
let convoRef = ref.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("conversations")
var conversationID = [String]()
print(1)
convoRef.queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
let enumerator = snapshot.children
print(2)
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(3)
if let id = rest.value as? String{
conversationID.append(id)
print(id)
}
}
})
print(4)
print("size: \(conversationID.count)")
for id in conversationID {
print(5)
ref.child("conversations").queryEqual(toValue: id).observeSingleEvent(of: .value, with: { (snapshot) in
print(6)
if let convo = snapshot.value as? [String : AnyObject] {
print(7)
let conversation = Conversation()
conversation.conversationID = id
conversation.name = "Temporary test name"
self.Conversations.append(conversation)
}
})
ref.removeAllObservers()
}
print(8)
self.conversationTableView.reloadData()
ref.removeAllObservers()
}
This prints:
1
4
size: 0
8
2
3
-KZyMMzXmkQC_OF0T08_
With the dispatch group:
func loadConversations() {
let dispatchGroup = DispatchGroup()
let ref = FIRDatabase.database().reference()
let convoRef = ref.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("conversations")
var conversationID = [String]()
print(1)
dispatchGroup.enter()
convoRef.queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
let enumerator = snapshot.children
print(2)
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(3)
if let id = rest.value as? String{
conversationID.append(id)
print(id)
dispatchGroup.leave()
}
}
})
print(4)
dispatchGroup.wait()
print("size: \(conversationID.count)")
for id in conversationID {
print(5)
ref.child("conversations").queryEqual(toValue: id).observeSingleEvent(of: .value, with: { (snapshot) in
print(6)
if let convo = snapshot.value as? [String : AnyObject] {
print(7)
let conversation = Conversation()
conversation.conversationID = id
conversation.name = "Temporary test name"
self.Conversations.append(conversation)
}
})
}
print(8)
self.conversationTableView.reloadData()
ref.removeAllObservers()
}
This prints
1
4
but then it just freezes and waits. The query never runs.
I am not sure why the query just does not appear to be entered. When the query is entered, it works perfectly fine, but it is entered too late. Any help is greatly appreciated. Thanks!
This is simply because Firebase queries are executed on a background thread as it is essentially a network call. Hence the response comes after your method completes, otherwise the UI will be blocked until a response comes from Firebase
You need to write a closure inside your query response to execute a block of code as soon as you get the response.
func loadConversations(completion:#escaping (Array<String>) -> Void) -> Void {
let ref = FIRDatabase.database().reference()
let convoRef = ref.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("conversations")
var conversationID = [String]()
print(1)
convoRef.queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot) in
let enumerator = snapshot.children
print(2)
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(3)
if let id = rest.value as? String{
conversationID.append(id)
print(id)
}
}
completion(conversationID)
})
}
This will send your call back to wherever it was called from and inside
loadConversations { (array) in
//do something with this value and execute next query
}
Related
I try to retrieve data from Firebase into Array. Because it runs asynchronously, the results that I want to show in my CollectionView is a delay until I switch back and forth. I am very new to asynchronous functions in iOS. Please help me to complete my code.
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
}
})
You need to reload the collection after you retrieve the data so after the for loop call reloadData()
for child in result {
}
self.collectionView.reloadData()
//
func getValueFromDatabase(completion: #escaping (_ status: Bool) -> Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion(true)
}
else {
completion(false)
}
})
}
//
self.getValueFromDatabase { (status) in
if status {
// success
}
}
I'm working with Firebase in my project right now. I would suggest the following solution: wrap the database observer in a distinct function which gets completion block as a parameter.
func getValueFromDatabase(completion: ()->Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion()
}
})
}
This way you can call the function from anywhere providing the desired action after fetching data from db is finished:
getValueFromDatabase(completion:{
self.collectionView.reloadData() //or any other action you want to fulfil
})
Please find my code below. How can we append filter data on array from Firebase?
var childrenList = [DatabaseList]()
let ref = Database.database().reference(withPath: "Messages")
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
for childSnapshot in snapshot.children{
print(childSnapshot)
self.childrenList.append(snapshot)
}
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
let ref = Database.database().reference(withPath: "Messages")
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
print(snapshot)
for (childSnapshotId, childSnapshotValue) in snapshot {
if let dataListDict = childSnapshotValue as? [String: AnyObject] {
//Init you newModel with the dataListDict here
let newModel = DatabaseList(dict: dataListDict)
print(childSnapshot)
self.childrenList.append(newModel)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
class DatabaseList : NSObject {
var messageBody : String?
var name : String?
var videoID : String?
init(dict: [String: AnyObject]) {
messageBody = dict["MessageBody"]
name = dict["Name"]
videoID = dict["videoID"]
}
}
Your query is correct but there are few mistakes in finishing block.
self.childrenList.append(snapshot) snapshot is an instance of DataSnapshot not a DatabaseList so you can not append it like this.
for childSnapshot in snapshot.children {
/// childSnapshot is an instance of DataSnapshot not a dictionary but its value will be
guard let data = (childSnapshot as! DataSnapshot).value else {continue}
let dataDict = data as! Dictionary<String, Any>
/// Initializing the new object of DatabaseList and passing the values from data
let list: DatabaseList = DatabaseList()
list.messageBody = dataDict["MessageBody"] as? String
list.name = dataDict["Name"] as? String
list.videoID = dataDict["VideoID"] as? String
/// This is correct, and now you can append it to your array.
childrenList.append(list)
}
Apart from this you will have to reload the tableView inside the finishing block not below the block because this is an asynchronous request and data will come later.
Also its always better to check the data existence. snapshot.exists().
One more suggestion if you want to fetch the data just once then do not use .observe use .observeSingleEvent instead. .observe will fire the block every time there is any change at this node.
Here is the full code snippet.
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
if !snapshot.exists() {
// Data doesn't exist
return
}
for childSnapshot in snapshot.children {
guard let data = (childSnapshot as! DataSnapshot).value else {continue}
let dataDict = data as! Dictionary<String, Any>
let list: DatabaseList = DatabaseList()
list.messageBody = dataDict["MessageBody"] as? String
list.name = dataDict["Name"] as? String
list.videoID = dataDict["VideoID"] as? String
childrenList.append(list)
}
/// Reload your tableView here
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
And expecting the class model like below:
class DatabaseList: NSObject {
var messageBody: String?
var name: String?
var videoID: String?
}
I want to use variable_a in another function. Actually, I want to load these data into tableviewcell.
func readFIRData() {
var credentials:[String]
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference().child("usr").child(userID!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let Name = value?["firstName"] as? String ?? ""
let PhoneNo = value?["mobile"] as? String ?? ""
var variable_a = [Name,PhoneNo]
self.tableView.reloadData()
}) { (error) in
}
}
You should have a callback (completion handler) in your readFIRData function, and pass variable_a as a parameter in that callback. Parse it into object which you use in table view, and reload tableView in the callback.
Function should look like this:
func readFIRData(_ completion: ([Name,PhoneNo]) -> ()) {
var credentials:[String]
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference().child("usr").child(userID!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let Name = value?["firstName"] as? String ?? ""
let PhoneNo = value?["mobile"] as? String ?? ""
var variable_a = [Name,PhoneNo]
completion(variable_a)
}) { (error) in
}
}
and then have another function which is going to call readFIRData function:
func requestData() {
readFIRData() { [weak self] data in
guard let `self` = self else { return }
self.data = data
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Mention how you get back to main thread to reload tableView. By self.data I assumed data which you will use in table view to instantiate cells.
Solved this way
func GetData(completion: #escaping(_ credentials: [String]) -> Void) {
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference().child("usr").child(userID!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let Name = value?["firstName"] as? String ?? ""
let PhoneNo = value?["mobile"] as? String ?? ""
var variable_a = [Name,PhoneNo]
completion(variable_a)
self.tableView.reloadData()
}) { (error) in
}
}
Now, assign StringArray to some variable. Use below code to assign. Likewise variable StringArrayVariable can be used to populate tableview cells.
GetData { (StringArray) in
self.StringArrayVariable= StringArray
}
How do I asynchronously observe a Firebase node? Right now, I'm using a DispatchGroup to fire off a closure once all the children in a node are observed.
I want to keep a listener on this node, because I expect the values to change in the future.
If I use a dispatchGroup, an update in the database causes the dispatchGroup.leave() to fire, without a corresponding .enter() causing my app to crash. Here is my code:
func fetchPosts(completion: #escaping ([Post]) -> Swift.Void) {
let dispatchGroup = DispatchGroup()
guard let uid = Auth.auth().currentUser?.uid else { return }
let dbRef = Database.database().reference()
let userPosts = dbRef.child("user-posts")
let postRef = dbRef.child("posts")
userPosts.child(uid).observe(.value, with: { (snap) in
if let dictionary = snap.value as? [String:Any] {
for item in dictionary {
dispatchGroup.enter()
postRef.child(item.key).observe(.value, with: { (snap) in
if let dictionary = snap.value as? [String:Any] {
let newPost = Post(dictionary: dictionary)
self.posts.append(newPost)
dispatchGroup.leave()
}
})
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
completion(self.posts)
}
})
}
func EmptySearchBarList(){
self.PersonalSearchesList = []
currentUserFirebaseReference.child("rooms").observeSingleEvent(of: .value) { (snapshot: FIRDataSnapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
print("snap")
print(self.PersonalSearchesList.count) // Calling 0 every single time
DataService.ds.REF_INTERESTS.child(interestKey).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
if snapshot.value != nil {
if let users = (snapshot.value! as? NSDictionary)?["users"] as? Dictionary<String,AnyObject> {
DataService.ds.REF_USERS.child(topUser).child("pictureURL").observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
self.PersonalSearchesList.append(eachSearch)
print("first one")
print(self.PersonalSearchesList.count)
})
}
}
})
}
print("Second one")
print(self.PersonalSearchesList.count)
print("Calling to set my sorted PersonalSearchesList")
self.mySortedList = self.PersonalSearchesList.sorted{ $0.users > $1.users }
}
}
initialLoadOver = true
}
What code I'm trying to ultimately run is this :
var mySortedList = [Interest](){
didSet{
print("this was called")
let myCount = mySortedList.count
print(self.PersonalSearchesList.count)
print(myCount)
self.tableView.reloadData()
}
}
The attempt is to load up my PersonalSearchesList array, and once the snap in snapshots is done running, I'm setting MySortedList equal to PersonalSearchesList and reloading the tableview.
What I don't understand is why the prints are coming out like they are. The snap/ 0's are coming from the top of my for snap in snapshots. It seems like it should instead be snap / 1 , snap / 2, snap / 3.
The code to be called when the snaps are done is correct in the timeline, once the snaps have gone through that code runs. What doesn't make sense is why it's not until after that the items are actually being appended to PersonalSearchesList. Becuase of how everything is Im' setting my filtered array to an empty personal searches array and then afterwards I'm filling it up.
Any ideas here?
edit:
var dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.leave()
dispatchGroup.notify(queue: DispatchQueue.global(), execute: {
})
DataService.ds.REF_INTERESTS.child(interestKey).observeSingleEvent is running asynchronous so all of those callbacks (where you actually fill you list) will run when they are called from the DataService.
You could use a dispatch group to do what you want.
http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/
func EmptySearchBarList(){
var dispatchGroup = DispatchGroup()
self.PersonalSearchesList = []
currentUserFirebaseReference.child("rooms").observeSingleEvent(of: .value) { (snapshot: FIRDataSnapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
dispatchGroup.enter()
single time
DataService.ds.REF_INTERESTS.child(interestKey).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
if snapshot.value != nil {
if let users = (snapshot.value! as? NSDictionary)?["users"] as? Dictionary<String,AnyObject> {
DataService.ds.REF_USERS.child(topUser).child("pictureURL").observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
self.PersonalSearchesList.append(eachSearch)
print("snap")
print(self.PersonalSearchesList.count)
})
}
}
dispatchGroup.leave()
})
}
dispatchGroup.notify(queue: DispatchQueue.global(), execute: {
print("Second one")
print(self.PersonalSearchesList.count)
print("Calling to set my sorted PersonalSearchesList")
self.mySortedList = self.PersonalSearchesList.sorted{ $0.users > $1.users }
})
}
}
initialLoadOver = true
}
I'm not completely sure you can enter and leave the group several times from the same thread so you might have to wrap the code from enter until the end of the callback in another thread and call it async