How to use Values after retrieving data with firebase in swift? - ios

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()
}
}
})
}

Related

Swift only stores last item in Array

I want to retrieve some data using firebase and store it in an empty array. Afterwards I have to store it in a global array (yes I know I shouldn't do that).
But the array only stores the last variable. I can assume that it is because the way firebase is retrieving the data in this function.
But i would like to have all the values appended to the Array.
Maybe someone can help me :)
static func jobs(for user: User, completion: #escaping ([TeamMember]) -> Void) {
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 dict = child.value as? [String: Any] else {
print("Error")
return completion([])
}
let memberJob = dict["memberJob"] as! String
print("memberJob: \(memberJob)")
var memberJobs: [String] = []
memberJobs.append(memberJob)
for job in memberJobs {
print("New Job incoming: ", job)
}
globalJobs = memberJobs
}
})
}
You create a new array every loop with var memberJobs: [String] = []
var memberJobs: [String] = []
memberJobs.append(memberJob)
Move this line out
var memberJobs: [String] = []
static func jobs(for user: User, completion: #escaping ([TeamMember]) -> Void) {
let ref = Database.database().reference().child("team").child(user.uid)
var memberJobs: [String] = [] /// hererererre
ref.observe(DataEventType.value, with: { (snapshot) in
for case let child as DataSnapshot in snapshot.children {
guard let dict = child.value as? [String: Any] else {
print("Error")
return completion([])
}
let memberJob = dict["memberJob"] as! String
print("memberJob: \(memberJob)")
memberJobs.append(memberJob)
for job in memberJobs {
print("New Job incoming: ", job)
}
}
globalJobs = memberJobs
})
}
You are creating a new empty array in each iteration of the loop.
Create it once before the loop and assign the array to globalJobs after the loop
static func jobs(for user: User, completion: #escaping ([TeamMember]) -> Void) {
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { (snapshot) in
var memberJobs: [String] = []
for case let child as DataSnapshot in snapshot.children {
guard let dict = child.value as? [String: Any] else {
print("Error")
return completion([])
}
let memberJob = dict["memberJob"] as! String
print("memberJob: \(memberJob)")
memberJobs.append(memberJob)
}
for job in memberJobs {
print("New Job incoming: ", job)
}
globalJobs = memberJobs
})
}
And the completion handler makes no sense if you call it only on failure.
This line is looping over each child in the snapshot
for case let child as DataSnapshot in snapshot.children {
inside that loop you create var memberJobs: [String] = [] which is an empty array. Then you add a member job so it has one item. You then assign this 1 item array to globalJobs = memberJobs so they are exactly the same. This assignment occurs over and over again for the loop for case let child as DataSnapshot in snapshot.children that is why you are only left with 1 item at then end.
You should declare var memberJobs: [String] = [] before the snapshot loop and assign globalJobs = memberJobs after the snapshot loop is done.

Completion handler needed for calling data from firebase?

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)
}

How do I observe the values of my Firebase database and use a closure?

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)
}
})
}

Firebase .childAdded observer duplicating 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()
}

How to create a function that inserts data into a block

First can someone help me come up with a better title? I just don't know the correct terminology on this one.
Here is my code,
func loadPublicFeed() {
ref = FIRDatabase.database().reference()
ref.child("brackets").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
if let bracketsSnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for brackets in bracketsSnapshot {
if let bracketsDict = brackets.value as? Dictionary <String, Any> {
let key = brackets.key as String
let post = BracketsPublicFeed(postKey:key, postData: bracketsDict)
self.posts.insert(post, at: 0)
}
}
}
self.stopRefresher()
self.collectionView.reloadData()
self.watchlistClicked = false
})
}
download data then do stuff. I want to take this function refactor it so I can just call the refactored function and add these
self.stopRefresher()
self.collectionView.reloadData()
self.watchlistClicked = false
into it.
It might look something like this,
func loadFeedTest() {
fetchTest.loadPublicFeed(collectionView: self.collectionView, completionHandler: { () -> Void in
self.stopRefresher()
self.collectionView.reloadData()
self.watchlistClicked = false
})
I have tried using a completionHandler something like func loadPublicFeed(completionHandler: () -> Void) then the code. I have done many variations of this. I feel like I might be on the right path but I just can't nail this down.
You can refactor your function in the following way:
func loadPublicFeed(collectionView: UICollectionView, completionHandler:() -> Void) {
ref = FIRDatabase.database().reference()
ref.child("brackets").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
if let bracketsSnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for brackets in bracketsSnapshot {
if let bracketsDict = brackets.value as? Dictionary <String, Any> {
let key = brackets.key as String
let post = BracketsPublicFeed(postKey:key, postData: bracketsDict)
self.posts.insert(post, at: 0)
}
}
}
completionHandler();
})
}

Resources