Completion handler Firebase observer in Swift - ios

I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}

The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}

This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().

Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}

func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}

Related

How can you ensure that cells are not duplicated when the device is overwhelmed by code from the previous page?

I have a problem that only occurs when page A has to run a massive amount of code and the user goes to page B before all the A page code is finished. In these instances, sometimes cells get duplicated(ie, say page B must be : User H in top, user F below him. Instead there are two Hs followed by two Fs below them).
Below is the relevant code of page B, but I am fairly certain the problem does not lie there. Why?: Because I changed the array that gets displayed [H,F] to a set, so according to the code, there should never be an instance like [H,H,F,F]
///Part1: code that gets called from viewdidLoad
var peppi = [Usery]()
func printPersonInfo(uid: String) {
self.handleA = thisUser.observe(DataEventType.value, with: { snapshot in
...
myPeopleRef44.queryLimited(toLast: 30).observeSingleEvent(of: .value, with: { [self] snapshot in
let uniqueArray = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray))
for person in peopleArray where uid == person.value as? String {
...
func decode(autoId: String) -> TimeInterval {
}
return TimeInterval(exactly: timestamp)!
}
...
if Calendar.current.isDateInToday(date){
let p = Usery(...)
peppi.append(p)
}
}
DispatchQueue.main.async {
self.peppi.sort { ($0.self.time1 ?? 0) > ($1.self.time1 ?? 0)
}
print(self.peppi, "lo")
self.table.reloadData()
}
})
})
}
/// Part: 2 In viewDidLoad, code that calls the function printPersonInfo
handle = myPeopleRef.queryLimited(toLast: 30).observe(DataEventType.value, with: { snapshot in
func decode(autoId: String) -> TimeInterval {
..
return …
}
let uniqueArray1 = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray1))
for person4 in peopleArray where uid == Auth.auth().currentUser?.uid {
self.dg.enter()
self.dg.leave()
self.dg.notify(queue: .main) {
let date = Date(timeIntervalSince1970: TimeInterval(time11)/1000.0)
print(date,"pdate")
if Calendar.current.isDateInToday(date){
self.printPersonInfo(uid: personUid)
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
let ref = Database.database().reference().child("people")
ref.removeObserver(withHandle: handle)
ref.removeObserver(withHandle: self.handleA)
}
})

Call reloadData() in the right place

I'm trying to fetch data from firebase and pass to tableview.
// Model
import UIKit
import Firebase
struct ProfInfo {
var key: String
var url: String
var name: String
init(snapshot:DataSnapshot) {
key = snapshot.key
url = (snapshot.value as! NSDictionary)["profileUrl"] as? String ?? ""
name = (snapshot.value as! NSDictionary)["tweetName"] as? String ?? ""
}
}
// fetch
var profInfo = [ProfInfo]()
func fetchUid(){
guard let uid = Auth.auth().currentUser?.uid else{ return }
ref.child("following").child(uid).observe(.value, with: { (snapshot) in
guard let snap = snapshot.value as? [String:Any] else { return }
snap.forEach({ (key,_) in
self.fetchProf(key: key)
})
}, withCancel: nil)
}
func fetchProf(key: String){
var outcome = [ProfInfo]()
ref.child("Profiles").child(key).observe(.value, with: { (snapshot) in
let info = ProfInfo(snapshot: snapshot)
outcome.append(info)
self.profInfo = outcome
self.tableView.reloadData()
}, withCancel: nil)
}
//tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profInfo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "followCell", for: indexPath) as! FollowingTableViewCell
cell.configCell(profInfo: profInfo[indexPath.row])
return cell
}
However it returns one row but profInfo actually has two rows. when I implement print(self.profInfo) inside fetchProf it returns two values. But after passed to tableview, it became one. I'm not sure but I guess the reason is that I put reloadData() in the wrong place because I hit break point and reloadData() called twice. So, I think profInfo replaced by new value. I called in different places but didn't work. Am I correct? If so, where should I call reloadData()? If I'm wrong, how can I fix this? Thank you in advance!
You need to append the new data to the profinfo array. Simply replace the fetchProf method with this:-
func fetchProf(key: String){
var outcome = [ProfInfo]()
ref.child("Profiles").child(key).observe(.value, with: { (snapshot) in
let info = ProfInfo(snapshot: snapshot)
outcome.append(info)
self.profInfo.append(contentOf: outcome)
Dispatch.main.async{
self.tableView.reloadData()
}
} , withCancel: nil)
}
self.tableView.reloadData() must be called from the main queue. Try
DispatchQueue.main.async {
self.tableView.reloadData()
}
if you notice one thing in the following function you will see
func fetchProf(key: String){
var outcome = [ProfInfo]()
ref.child("Profiles").child(key).observe(.value, with: { (snapshot) in
let info = ProfInfo(snapshot: snapshot)
outcome.append(info)
//Here
/You are replacing value in self.profInfo
//for the first time when this is called it results In First profile info
//When you reload here first Profile will be shown
//Second time when it is called you again here replaced self.profInfo
//with second Outcome i.e TableView reloads and output shown is only second Profile
//you had initialised a Array self.profInfo = [ProfInfo]()
//But you just replacing array with Single value Actually you need to append data
// I think here is main issue
self.profInfo = outcome
//So try Appending data as
//self.profInfo.append(outcome) instead of self.profInfo = outcome
//Then reload TableView to get both outputs
self.tableView.reloadData()
}, withCancel: nil)
}
Table view showing one content because when table view reloaded then profile info not combine all data. You need to reload the table view after combining all data. This will help you.
// fetch
var profInfo = [ProfInfo]()
func fetchUid(){
guard let uid = Auth.auth().currentUser?.uid else{ return }
ref.child("following").child(uid).observe(.value, with: { (snapshot) in
guard let snap = snapshot.value as? [String:Any] else { return }
snap.forEach({ (key,_) in
self.fetchProf(key: key)
})
// When all key fetched completed the just reload the table view in the Main queue
Dispatch.main.async{
self.tableView.reloadData()
}
}, withCancel: nil)
}
func fetchProf(key: String){
ref.child("Profiles").child(key).observe(.value, with: { (snapshot) in
let info = ProfInfo(snapshot: snapshot)
self.profInfo.append(info) // Here just add the outcome object to profileinfo
}, withCancel: nil)
}
This way no need to handle another array.

Updating values in Firebase

func updateFirebase(){
myFun = thisIsMyFunTextView.text
IAm = iAmTextView.text
var profileKey = String()
profileRef.queryOrdered(byChild: "uid").queryEqual(toValue: userID).observe(.value, with:{
snapshot in
for item in snapshot.children {
guard let data = item as? FIRDataSnapshot else { continue }
guard let dict = data.value as? [String: Any] else { continue }
guard let profileKey = dict["profileKey"] else { continue }
self.profileRef.child(profileKey as! String).child("bodyOfIAM").setValue(IAm)
self.profileRef.child(profileKey as! String).child("bodyOfThisIsMyFun").setValue(myFun)
}
})
}
#IBAction func backButtonClicked(_ sender: Any) {
updateFirebase()
DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
self.dismiss(animated: true)
})
}
myFun and IAm are successfully defined by the changes to the textviews by the user. I can't extract the childByAutoID value without triggering this for in loop that does not end once called, continuing even as a new view controller is presented. The "bodyOfThisIsMyFun" vacillates between the old value and the new value during this loop while the "bodyOfIAM" gets correctly redefined right away and stays that way like it should. How do I get the extracted new values to replace the old values here?
I needed to add this line of code at the end of the for...in statement:
self.profileRef.removeAllObservers()

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

How to cancel a firebase observe single event database call

In my iOS app, a user is able to add friends by searching for there unique username.
The user types the username in a textField and I have a textFieldDidChange notification which is fired every time the text changes.
Within that method I then call the Firebase method below to check if the username exists.
func searchFor(_ username: String) {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let lowercaseUsername = username.lowercased()
let ref = FIRDatabase.database().reference()
ref.child(FirebaseDatabaseBranchNames.usernames.rawValue).child(lowercaseUsername).observeSingleEvent(of: .value, with: { [unowned self](snapshot) in
if snapshot.exists() {
if let usernameUid = snapshot.value as? String {
self.isUserAlreadyAFriend(ref, uid: uid, usernameUid: usernameUid)
}
} else {
// username doesn't exist
}
}, withCancel: nil)
}
How can I cancel this method, before performing it again?
When you attach a listener/observer, Firebase returns a handle for that observer. You can subsequently remove the listener/observer by calling ref.removeObserverWithHandle().
So assuming you want at most one observer, you can keep the reference and observer handle in a member field of you class and then use this code in the searchFor method:
if (self.searchHandle != nil) {
self.searchRef.removeObserverWithHandle(searchHandle)
}
self.searchRef = ref.child(FirebaseDatabaseBranchNames.usernames.rawValue).child(lowercaseUsername)
self.searchHandle = self.searchRef.observeSingleEvent(of: .value, with: { [unowned self](snapshot) in
if snapshot.exists() {
if let usernameUid = snapshot.value as? String {
self.isUserAlreadyAFriend(ref, uid: uid, usernameUid: usernameUid)
}
} else {
// username doesn't exist
}
}, withCancel: nil)
Be aware that you won't be saving data transfer with this though, as the most likely result is that the database client simply drops the data that it gets back from the server.
Better you add the "removeAllObservers()" after the observe single event block. It is working for me.
let ref = Database.database().reference().ref.child(XXXX).child(YYYYY)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
}else{
}
}) { (error) in
print(error.localizedDescription)
}
ref.removeAllObservers()

Resources