Im trying to call data from firebase. The problem is, the data is deeply nested and I don't think I can change that.
So I'm attempting to call values from firebase, which I can then use to reference new values.
The problem arises when my for loop is not finished before the next stage is called, meaning my dictionary count for the next stage is 0, so my next function is not called?
Is there a way to do this sufficiently?
Please help?
Heres my code:
func fetchBuyer(search: String, user: String, completion: #escaping ([Post]) -> (), withCancel cancel: ((Error) -> ())?) {
let ref = Database.database().reference().child("posts").child(user).child(search).child("purchases")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any] else {
completion([])
return
}
let keys: [String] = dictionaries.map({ $0.key })
var newdictionaries = [String: String]()
for i in keys {
let newref = Database.database().reference().child("posts").child(user).child(search).child("purchases").child(i).child("purchaser")
newref.observeSingleEvent(of: .value, with: { (snapshot) in
newdictionaries[i] = snapshot.value as? String
print("THESE ARE MY PURCHASES ID-->", newdictionaries.values)///prints out ["-M0pTHtZXYUVQT7DCLj-", "-M0pU79uQCCnBunAEkJN"]
})
}
var buyerPosts = [Post]()
print("newdictionaries.count--->", newdictionaries.count)//this print is 0
newdictionaries.forEach({ (postId, value) in
Database.database().fetchPost(withUID: user, postId: postId, completion: { (post) in
buyerPosts.append(post)
if buyerPosts.count == newdictionaries.count{
completion(buyerPosts)
}
})
})
}) { (err) in
print("Failed to fetch posts for buyers:", err)
cancel?(err)
}
}
Attempted answer:
let g = DispatchGroup() //// 1
for i in keys{
g.enter() //// 2
let newref = Database.database().reference().child("posts").child(user).child(search).child("purchases").child(i).child("purchaser")
print("now")
newref.observeSingleEvent(of: .value, with: { (snapshot)
newdictionaries[i] = snapshot.value as? String
print("print new dictionaries-->", newdictionaries)
// complete here
Database.database().fetchPost(withUID: user, postId: newdictionaries[i]!, completion: { (post) in
buyerPosts.append(post)
g.leave() //////// 3
})
})
}
g.notify(queue: DispatchQueue.main) {
print("finished!!!")
completion(buyerPosts)
}
You need a dispatch group and nest the calls
let g = DispatchGroup() //// 1
for i in keys{
g.enter() //// 2
let newref = Database.database().reference().child("posts").child(user).child(search).child("purchases").child(i).child("purchaser")
newref.observeSingleEvent(of: .value, with: { (snapshot)
newdictionaries[i] = snapshot.value as? String
// complete here
Database.database().fetchPost(withUID: user, postId: postId, completion: { (post) in
buyerPosts.append(post)
g.leave() //////// 3
})
})
}
/// 4
g.notfiy(queue.main) {
completion(buyerPosts)
}
Related
so far I have always worked with the data i am retrieving from firebase without having to do something with them besides showing.
Now I would actually need to store the data in another array and/ or also in general I really wonder how to actually work with the data.
My approach right now looks like this but its actually not working.
Does anyone know how to do it?
....
class ProjectCharacterViewController: UIViewController {
// MARK: - Properties
var soloJobs: [String] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getJobs(for: User.current) { (memberJob) in
self.uniqueJobs = memberJob
}
}
...
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
var jobs: [String] = []
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([])
}
let memberJob = value["memberJob"] as! String
jobs.append(memberJob)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
soloJobs = jobs
}
Since the observe function is an asynchronous call, your soloJobs = jobs is getting called before it. That is why it is returning an empty array, because, at that time, your jobs array is also empty. Consider calling it inside your for loop as so:
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
var jobs: [String] = []
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([])
}
let memberJob = value["memberJob"] as! String
jobs.append(memberJob)
self.soloJobs = jobs
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
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
}
Is it possible to fetch all child nodes from parent in firebase db?
Trying to say this:
I want to get all posts at the same time.
It fetches videos but only the current users videos. I want to fetch ALL users videos at the same time.
Here's some code to get more understanding of what I'm doing:
fileprivate func fetchAllPost() {
fetchPosts()
fetchAllPostsFromUserIds()
}
fileprivate func fetchAllPostsFromUserIds() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("posts").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
FIRDatabase.fetchUserWithUid(uid: key, completion: { (user) in
self.fetchPostsWithUser(user: user)
})
})
}) { (err) in
print("failed to fetch following users ids:", err)
}
}
var posts = [Post]()
fileprivate func fetchPosts() {
guard let currentUserID = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.fetchUserWithUid(uid: currentUserID) { (user) in
self.fetchPostsWithUser(user: user)
}
}
fileprivate func fetchPostsWithUser(user: User) {
let ref = FIRDatabase.database().reference().child("posts/\(user.uid)/")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
guard let dictionaries = snapshot.value as? [String: Any] else { return }
dictionaries.forEach({ (key,value) in
guard let dictionary = value as? [String: Any] else { return }
var post = Post(user: user, dictionary: dictionary)
post.id = key
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.database().reference().child("likes").child(key).child(uid).observe(.value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
self.posts.append(post)
self.posts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
}, withCancel: { (err) in
print("Failed to fetch info for post")
})
print(self.posts)
})
}) { (error) in
print("Failed to fetch posts", error)
}
}
I don't know Swift, but you could fetch FIRDatabase.database().reference().child("posts") and then iterate over children.
I am trying to have my program add every instance of a child to an array but whenever I delete a child then add another it duplicates that added child twice and if I do it again it will duplicate 3 times and so on depending on how many times I delete and add. The adding and deleting is mainly handled in the function below and I can't figure out why it is duplicating them.
func fetchContacts(completion: #escaping () -> ()){
contacts = [Contact]()
let userRef = ref.child("users").child(user).child("contacts")
userRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
print(snapshot)
let contact = Contact()
contact.setValuesForKeys(dictionary)
self.contacts.append(contact)
}
self.contactsTable.reloadData()
completion()
}, withCancel: nil)
userRef.observe(.childRemoved, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let contact = Contact()
contact.setValuesForKeys(dictionary)
if let i = self.contacts.index(where: { $0.name == contact.name }) {
self.contacts.remove(at: i)
}
}
self.contactsTable.reloadData()
completion()
}, withCancel: nil)
}
Here is where the deleting is handled and how the functions are called in the viewDidLoad:
override func viewDidLoad() {
contactsTable.delegate = self
contactsTable.dataSource = self
contactsTable.rowHeight = 65
super.viewDidLoad()
fetchContacts(){
self.contactsTable.reloadData()
}
}
func handleDelete(phone: String, completion: #escaping () -> ()){
let userRef = ref.child("users").child(user).child("contacts")
userRef.child(phone).removeValue { (error, ref) in
if error != nil {
print("Error: \(error)")
}
completion()
}
}
This may be to do with you not calling reloadData() on the main thread.
Instead of just:
self.contactsTable.reloadData()
try:
DispatchQueue.main.async {
self.contactsTable.reloadData()
}
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
}