Swift only stores last item in Array - ios

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.

Related

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

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

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 to Append Filter Data on Array from Firebase in Swift

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

How to use the variable of FirebaseReference.observeSingleEvent into another function

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 to make UIRefreshControl end refreshing when finish loading from Firebase

The following method is my refreshing data method. It will read from the firebase to get the newest data. Because firebase is unsynchronized so I wait 5 seconds before endRefreshing to wait for Firebase to finish the reading process.
func refreshData() {
//Remove old data
self.items.removeAll()
//Renew all data
var ref: DatabaseReference!
ref = Database.database().reference(withPath: "tasks")
//Loading local drafts
var drafts : [Task]!
if let local_drafts = NSKeyedUnarchiver.unarchiveObject(withFile: Task.ArchiveURL.path) as? [Task] {
drafts = local_drafts
}
else{
drafts = []
}
//Reloading the database
ref.observe(.value, with: { snapshot in
var newItems: [Task] = []
self.num_of_tasks = Int(snapshot.childrenCount)
for item in snapshot.children {
//let local = item as! DataSnapshot
//let snapshotValue = local.value as! [String: AnyObject]
//print(snapshotValue["main_content"] as! String!)
let taskItem = Task(snapshot: item as! DataSnapshot)
newItems.append(taskItem!)
}
let merged = drafts + newItems
self.items = merged
self.tableView.reloadData()
//Wait for 5 seconds
let deadlineTime = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadlineTime, execute: {
self.refreshControl!.endRefreshing()
})
})
}
But this is not a good solution because sometimes all data is loaded but the refreshing is still continuing, which makes it strange. Is there a way to solve this kind of situation? Make the refreshControl end refreshing until I load all data from firebase?
Beneath is one way to do it. It will wait until the async data has finished downloaded, and than when the completionHandler return either true or false, the refreshControl will stop refresh. I do not had a compiler here so I hope it works without errors.
func viewDidLoad(){
refreshData{ _ in
self.refreshControl!.endRefreshing()
}
}
func refreshData(completionHandler:#escaping (Bool)->() ) {
//Remove old data
self.items.removeAll()
//Renew all data
var ref: DatabaseReference!
ref = Database.database().reference(withPath: "tasks")
//Loading local drafts
var drafts : [Task]!
if let local_drafts = NSKeyedUnarchiver.unarchiveObject(withFile: Task.ArchiveURL.path) as? [Task] {
drafts = local_drafts
}
else{
drafts = []
}
//Reloading the database
ref.observe(.value, with: { snapshot in
var newItems: [Task] = []
self.num_of_tasks = Int(snapshot.childrenCount)
for item in snapshot.children {
//let local = item as! DataSnapshot
//let snapshotValue = local.value as! [String: AnyObject]
//print(snapshotValue["main_content"] as! String!)
let taskItem = Task(snapshot: item as! DataSnapshot)
newItems.append(taskItem!)
}
let merged = drafts + newItems
self.items = merged
self.tableView.reloadData()
completionHandler(true)
})
}

Resources