How to know if a Firebase has fetched a value - ios

I have three functions getNewOrder(),storeOrderDetails(_ details:[String:String]) and getUserInfo(_ userID:String).
Function getNewOrder() is called first. It fetches new orders ( .childAdded values) and sends the dictionary to storeOrderDetails(_ details:[String:String]).
storeOrderDetails(_ details:[String:String])then segregate all the values and callsgetUserInfo(_ userID:String)` by passing it userID which was present in its details.
getUserInfo(_ userID:String) then fetches users details and returns user's
information.
However, the problem is [ userInfo = getUserInfo(_ userID:String) in storeOrderDetails(_ details:[String:String]) ] userInfo is always empty. Apparently func getUserInfo(_ userID:String) goes into a completion block after it has returned empty value.
I want these three functions to execute in sequential way.
Any advice is highly appreciated.
Please follow the below Links to review my code.
https://imgur.com/hNjvyDk
https://imgur.com/J0LMXMg
func childAdded(){
let ref = Database.database().reference().child("Orders").child(todaysDate)
ref.observe(.childAdded) { (snapshot) in
var details = [String:String]()
if let orderID = snapshot.key as? String {
ref.child(orderID).observeSingleEvent(of: .value, with: { (snap) in
self.newOrderTextView.text = ""
self.customerNameLabel.text = ""
self.customerPhoneLabel.text = ""
self.orderNumberLabel.text = ""
let enumerator = snap.children
while let rest = enumerator.nextObject() as? DataSnapshot {
details[rest.key as? String ?? ""] = rest.value as? String ?? ""
}
self.storeUserDetails(details)
})
}
}
}
func storeUserDetails(_ details:[String:String]){
if details["CustomerID"] != nil {
userInfo = getUserDetails(details["CustomerID"]!)
print(userInfo)
}
if !userInfo.isEmpty{
let order = OrderDatabase()
order.customerEmail = userInfo["Email"]!
order.customerName = userInfo["Name"]!
order.orderAcceptStatus = details["OrderStatus"]!
order.customerOrderNumber = details["orderNumber"]!
order.orderID = details["orderID"]!
order.time = details["Time"]!
order.customerFirebaseID = details["CustomerID"]!
self.orderDatabase[details["orderNumber"]!] = order
self.orderTable.reloadData()
}
}
func getUserDetails(_ userID:String) -> [String:String]{
var details = [String:String]()
let userDetailsReference = Database.database().reference().child("Users")
userDetailsReference.child(userID).observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if let dictionary = snapshot.value as? NSDictionary {
self.customerNameLabel.text = dictionary.value(forKey: "Name") as? String
self.customerPhoneLabel.text = dictionary.value(forKey: "Email") as? String
details["Name"] = dictionary.value(forKey: "Name") as? String
details["Email"] = dictionary.value(forKey: "Email") as? String
}
})
return details
}

From what I can see here, I am betting that the issue you are facing has to do with the fact that the methods are asynchronous. So one thing is not completely finished and some other method gets fired too soon. There are a few ways to deal with this issue. One is completion handlers, and the other is adding observers. Below is an example of doing both for Firebase. Here I'm asking a getLocationPhotos method to get all the photos from Firebase. Notice the observers and completion handler
func getLocationPhotos(coordinate:CLLocationCoordinate2D){
dbHandler.getImageFileNames(coordinateIn: coordinate) { (filenames) in
if filenames.isEmpty {
log.debug(String.warningGet + "filenames is empty")
return
}//if filenames.isEmpty
self.imageFiles = filenames.filter { $0 != "none" }
if self.imageFiles.isEmpty {
log.error(String.errorGet + "imageFiles array is empty")
return
}//if imageFiles.isEmpty
for file in self.imageFiles {
let reference = self.storageHandler.imageReference.child(file)
let download = self.imageView.sd_setImage(with: reference)
if let i = self.imageView.image {
self.imageArray.append(i)
self.collectionView.reloadData()
}//let i
download?.observe(.progress, handler: { (snapshot) in
guard let p = snapshot.progress else {
return
}//let p
self.progressView.progress = Float(p.fractionCompleted)
if self.progressView.progress == Float(1) {
self.progressView.isHidden = true
}
})//progress
download?.observe(.success, handler: { (snapshot) in
self.progressView.progress = 1
self.progressView.isHidden = true
self.collectionView.setNeedsLayout()
})//success
download?.observe(.failure, handler: { (snapshot) in
log.error(String.errorGet + "Error occured getting data from snapshot")
})//failure
}//for file
}//dbHandler

Related

Two functions that get data from a FireBase database, and a third function that performs some calulations

I have two functions that successfully retrieve integers from Firebase. I'd like a third function that does some simple subtraction from the integers gathered in the first two functions.
However, I'm very new to this, so can't get it to work correctly.
The output of the two functions that gather data from Firebase are:
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
and
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
What I'd like is a third function that looks like this:
let pointsBalance = sumOfPointsCompleted - pointsRedeemedAsInt
However, the third function doesn't recognise sumOfPointsCompleted, nor pointsRedeemedAsInt.
// First Function:
func loadPointsRedeemed() {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
}
)}
//Second Function:
func LoadPointsCompleted() {
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
}
}
}
)}
// Third Function (which does not work):
func BalanceOfPoints(){
let balance = sum - pointsRedeemedAsInt
}
The error is:
Use of unresolved identifiers sum and pointsRedeemedAsInt
Furthermore, how do I ensure that everything is executed in the right order? ie, the loadPointsCompleted function must run (and complete) first, followed by the loadPointsRedeemed function, and finally the BalanceOfPoints function.
Actually, the problem is that you are not considering that retrieving data from remote sources is asynchronous.
This means that you have to wait for data to be retrieved before calling the other functions.
To achieve this result, you should use swift closure (callback in other languages) with completion handler. Check this documentation.
Change your functions this way:
First Function
func loadPointsRedeemed(completion: #escaping (_:Int)->()){
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
//Call your return back function called "completion"
completion(pointsRedeemedAsInt)
}
)}
Second Function
func loadPointsCompleted(completion: #escaping (_:Int)->()){
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
}
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
completion(sumOfPointsCompleted)
}
}
)}
Third Function
func balanceOfPoints(completion: #escaping (_:Int)->()) {
loadPointsCompleted{(sum) in
//HERE YOU CAN USE THE RESULT OF loadPointsCompleted
//I CALLED IT sum
loadPointsRedeemed{ (pointsRedeemedAsInt) in
// HERE YOU CAN USE THE RESULT OF loadPointsRedeemed
//I CALLED IT pointsRedeemedAsInt
let balance = sum - pointsRedeemedAsInt
completion(balance)
}
}
}
To call the balance function wherever you want:
balanceOfPoints{ (balance) in
// Whatever you want with balance
}
If you change the view ( for example you set some label text ), be sure to use the functions in the main thread.
The problem is that you are trying to access variables outside the scope of BalanceOfPoints().
Try returning the values you want to use in the equation from the first two functions, loadPointsRedeemed() and LoadPointsCompleted(). This can be done like so:
First Function
func loadPointsRedeemed() -> Int {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
return pointsRedeemedAsInt
}
)}
Second Function
func loadPointsCompleted() -> Int {
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
}
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
return sumOfPointsCompleted
}
}
)}
Third Function
func balanceOfPoints() -> Int {
let sum = loadPointsCompleted()
let pointsRedeemedAsInt = loadPointsRedeemed()
let balance = sum - pointsRedeemedAsInt
return balance
}
Now, wherever you call the functions loadPointsRedeemed() and loadPointsCompleted(), replace these calls with balanceOfPoints.
Notice the main changes I made to your code are adding return values to your functions so they can be used in other areas of your code. Check out the Swift Functions Documentation to learn more.

Wrong read from Firebase

I am uploading a product to Firebase using this code :
let storageRef = Storage.storage().reference().child("ProductsImages").child(product.UniqueID()).child("MainImage.png")
if let mainChosenImage = self.selectedImageToUpload
{
if let uploadData = UIImageJPEGRepresentation(mainChosenImage, 0.2)
{
storageRef.putData(uploadData, metadata: nil)
{
(StorageMetaData, error) in
if error != nil
{
print(error)
return
}
self.mainImageURL = StorageMetaData?.downloadURL()?.absoluteString
if let urlString = self.mainImageURL
{
self.ref.child("Products").child(product.UniqueID()).child("MainImage").setValue(urlString)
self.ref.child("Users").child(user.uid).child("Products").child(product.UniqueID()).child("MainImage").setValue(urlString)
product.AddImageURLToProduct(URL: urlString)
}
}
}
}
product.RegisterProductForAllUsers(database: self.ref)
product.RegisterProductForAddingUser(database: self.ref)
self.performSegue(withIdentifier: "unwindToMyProductsViewController", sender: self)
Now I know that writing an image like this is async (1), but after item is added (Let's say we ignore picture for now), I have this in Firebase:
saved Firebase Product
But when I go back to my collectionView and load the information (It happens in the ViewDidLoad method), this is the information I read:
Product information read
This is my code for ViewDidLoad:
if let currentUserID = loggedOnUserID
{
// Retrieve the products and listen for changes
databaseHandle = ref?.child("Users").child(currentUserID).child("Products").observe(.childAdded, with:
{ (snapshot) in
// Code to execute when new product is added
let prodValue = snapshot.value as? NSDictionary
let prodName = prodValue?["Name"] as? String ?? ""
let prodPrice = prodValue?["Price"] as? Double ?? -1
let prodDesc = prodValue?["Description"] as? String ?? ""
let prodURLS = prodValue?["MainImage"] as? String ?? ""
let prodAmount = prodValue?["Amount"] as? Int ?? 0
let prodID = snapshot.key
let prodToAddToView = Product(name: prodName, price: prodPrice, currency: "NIS", description: prodDesc, location: "IL",
toSell: false, toBuy: false, owner: currentUserID, uniqueID: prodID, amount: prodAmount)
if (prodURLS != "")
{
prodToAddToView.AddImageURLToProduct(URL: prodURLS)
}
self.products.append(prodToAddToView)
DispatchQueue.main.async
{
self.MyProductsCollection.reloadData()
}
}
) // Closes observe function
Also - my code writing to Database :
public func RegisterProductForAllUsers(database dataBase: DatabaseReference)
{
dataBase.child("Products").child(self.UniqueID()).child("Name").setValue(self.Name())
dataBase.child("Products").child(self.UniqueID()).child("UniqueID").setValue(self.UniqueID())
dataBase.child("Products").child(self.UniqueID()).child("Price").setValue(self.Price())
dataBase.child("Products").child(self.UniqueID()).child("Description").setValue(self.Description())
dataBase.child("Products").child(self.UniqueID()).child("ToBuy?").setValue(self.m_ToBuy)
dataBase.child("Products").child(self.UniqueID()).child("ToSell?").setValue(self.m_ToSell)
dataBase.child("Products").child(self.UniqueID()).child("Owner").setValue(self.m_Owner)
dataBase.child("Products").child(self.UniqueID()).child("Amount").setValue(self.m_Amount)
dataBase.child("Products").child(self.UniqueID()).child("MainImage").setValue(self.m_PicturesURLs.first)
}
I am writing "Name" first, which is maybe the reason I only read name properly? Is there a way to make all these writings be atomic ?
with only 1 value for some reason. (2)
1) Any way to make it sync ?
2) How can I read the proper values ?

Access childAutoID to update selected child value in Firebase

In order to populate my tableView, I append items (created from a struct) to a local array:
func loadList() {
var newAnnotations: [AnnotationListItem] = []
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").queryOrderedByKey().observeSingleEvent(of: .value, with: {snapshot in
for item in snapshot.children {
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
annotationList = newAnnotations
self.tableView.reloadSections([0], with: .fade)
})
}
}
When I click a specific row, I am taken to a DetailViewController where it is only a large UITextView (named notes). The UITextView.text displayed is based on the selected indexPath.row and the "notes" value is retrieved from the array. Now the user is able to type some text and when they are done, the textViewDidEndEditing function is called:
func textViewDidEndEditing(_ textView: UITextView) {
notes.resignFirstResponder()
navigationItem.rightBarButtonItem = nil
let newNotes = self.notes.text
print(newNotes!)
}
Now I'd like to updateChildValues to newNotes to the child node "notes" in my JSON:
"users" : {
"gI5dKGOX7NZ5UBqeTdtu30Ze9wG3" : {
"annotations" : {
"-KuWIRBARv7osWr3XDZz" : {
"annotationSubtitle" : "1 Cupertino CA",
"annotationTitle" : "Apple Infinite Loop",
"notes" : "Does it work?!",
}
How can I access the selected autoID so I can update the specific notes node. So far the best I have is:
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(somehow access the specific childID).updateChildValues(["notes": newNotes])
Any help will be greatly appreciated. Thanks in advance
UPDATE
The annotationListItem struct is created:
struct AnnotationListItem {
let key: String?
var annotationTitle: String?
let annotationSubtitle: String?
let notes: String?
let ref: DatabaseReference?
init(key: String = "", annotationTitle: String, annotationSubtitle: String, notes: String) {
self.key = key
self.annotationTitle = annotationTitle
self.annotationSubtitle = annotationSubtitle
self.notes = notes
self.ref = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
annotationTitle = snapshotValue["annotationTitle"] as? String
annotationSubtitle = snapshotValue["annotationSubtitle"] as? String
notes = snapshotValue["notes"] as? String
ref = snapshot.ref
}
init(Dictionary: [String: AnyObject]) {
self.key = Dictionary["key"] as? String
self.annotationTitle = Dictionary["annotationTitle"] as? String
self.annotationSubtitle = Dictionary["annotationSubtitle"] as? String
self.notes = Dictionary["notes"] as? String
self.ref = nil
}
func toAnyObject() -> Any {
return [
"annotationTitle": annotationTitle as Any,
"annotationSubtitle": annotationSubtitle as Any,
"notes": notes as Any
]
}
}
UPDATE
This is how the annotationListItem is created to be stored in Firebase:
// Using the current user’s data, create a new AnnotationListItem that is not completed by default
let uid = Auth.auth().currentUser?.uid
guard let email = Auth.auth().currentUser?.email else { return }
let title = placemark.name
let subtitle = annotation.subtitle
let notes = ""
// declare variables
let annotationListItem = AnnotationListItem(
annotationTitle: title!,
annotationSubtitle: subtitle!,
notes: notes)
// Add the annotation under their UID
let userAnnotationItemRef = uidRef.child(uid!).child("annotations").childByAutoId()
userAnnotationItemRef.setValue(annotationListItem.toAnyObject())
I think you only need to do this:(since you have declared the note as global)
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(note.key).updateChildValues(["notes": newNotes])
inside the method where you change the notes
If I am not mistaken you are creating an array of a custom object?
var newAnnotations: [AnnotationListItem] = []
You could do something like: var newAnnotations: [(key: String, value: [String : Any])] = [] (Any only if you are going to have Strings, Integers, ect. If it'll only be String then specify it as a String.
Accessing the key would be: newAnnotations[indexPath.row].key in your cellForRowAtIndex of your tableView. Accessing values would be: newAnnotations[indexPath.row].value["NAME"].
You can have a separate array that holds the key and just append it at the same time as your population:
for item in snapshot.children {
guard let itemSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key //This is the ID
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
Another thing you could do is go up one more level in your firebase call:
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: {snapshot in
if snapshot is NSNull{
//Handles error
} else{
if let value = snapshot.value as? NSDictionary{ //(or [String: String]
//set localDictionary equal to value
}
}
self.tableView.reloadSections([0], with: .fade)
})
}
And then when you select a row: let selectedItem = localDictionary.allKeys[indexPath.row] as! String //This is the ID you pass to your viewController.

Asynchronous Data in tableView Firebase Swift

I have an issue with my least favourite part in Firebase. I want to pull a post from user's following list (every user has one and only one post). First, I created a completion handler to get a list of all followers from Firebase and store it in userArray array of strings:
func GetUsersInFollowing(completion: #escaping (Bool) -> ()) {
ref.child("following").queryOrdered(byChild: FIRAuth.auth()!.currentUser!.uid).observeSingleEvent(of: .value, with: { (snapshot) in
for group in snapshot.children {
self.userArray.append((group as AnyObject).key)
}
completion(true)
})
}
Now the plan is to pull a post from every element of userArray.
Here is where the problem starts. I call CreatePosts() immediately after GetUsersInFollowing() completes.
func CreatePosts() {
for x in userArray {
var thePost = Post()
print("1")
self.ref.child("users").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2")
})
self.ref.child("posts").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
})
self.postArray.append(thePost)
self.tableView.reloadData()
}
}
Everything looks ok to me, but it surely isn't. Here's how I create cells:
func configureCell(post: Post) {
self.post = post
self.username.text = post.username
self.profileImage = post.profileImageURL
print("3")
self.fullname.text = post.fullName
self.timestamp.text = post.timePost
self.upvotes.text = post.upVotes
self.location.text = post.location
self.descriptionText.text = post.description
}
The output in the console varies, but usually I get:
1
1
3
3
2
2
The idea is to first retrieve all data from Firebase, add it to post object, append the object to the array and then create cell for that object with data downloaded. Cell is already created even though data is not retrieved. I think that is the problem. Thank you, every suggestion is appreciated.
You need to inner query for combining both user profile data and post data.
Like this -
func CreatePosts() {
//Using userPostArrayObjFetched as a counter to check the number of data fetched.
//Remove this code, if you don't want to wait till all the user data is fetched.
var userPostArrayObjFetched = 0
for (index,userID) in userArray.enumerated() {
print("1" + userID)
self.ref.child("users").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
var thePost = Post()
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2" + userID)
self.ref.child("posts").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
print("3" + userID)
self.postArray.append(thePost)
// Uncomment if you want to reload data as fetched from Firebase without waiting for all the data to be fetched.
// self.tableView.reloadData()
userPostArrayObjFetched += 1
if userPostArrayObjFetched == userArray.count{
self.tableView.reloadData()
}
})
})
}
}

leaky listener firebase ios

I am trying to load message box data for chat functionality.
The message box is loaded as:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser()!["firebaseUID"] !== nil)
{
print(PFUser.currentUser()!["firebaseUID"])
self.updateResultArray(PFUser.currentUser()!["firebaseUID"] as! String)
}
}
func updateResultArray(uid: String) {
let userName = String(PFUser.currentUser()!["username"])
//print("updateResultArray is getting called")
let userhandle = self.firebase.childByAppendingPath("users").childByAppendingPath(uid).childByAppendingPath("rooms").queryOrderedByValue()
.observeSingleEventOfType(.Value, withBlock: { roomsnapshot in
let enumerator = roomsnapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
self.roomArray.append(rest.key)
}
//get the latest message from all the rooms
if self.roomArray.isEmpty == false
{
for i in 0...self.roomArray.count-1
{
print("in the room loop \(self.roomArray[i])")
let messagehandle = self.messagesRef.childByAppendingPath(self.roomArray[i]).queryOrderedByKey().queryLimitedToFirst(1).observeSingleEventOfType(.Value, withBlock: {
messagesnapshot in
print("the messagesnapshot child count is \(messagesnapshot.childrenCount)")
let enumerator = messagesnapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
let sender = rest.value.objectForKey("sender") as? String
let reciever = rest.value.objectForKey("reciever") as? String
//print("sender is \(sender!) and reciever is \(reciever!)")
let eventhandle = self.firebase.childByAppendingPath("rooms").childByAppendingPath(self.roomArray[i]).observeSingleEventOfType(.Value, withBlock: { eventsnapshot in
if eventsnapshot.value is NSNull {
// The value is null
}
else
{
let eventAttr = eventsnapshot.value.objectForKey("eventAttributes") as? String
let eventDetails = eventsnapshot.value.objectForKey("eventDetails") as? String
//print("userName is \(userName)")
//print("sender is \(sender)")
if (userName != sender!) //for event joinee
{
let firstname1 = eventsnapshot.value.objectForKey("firstname1") as? String
self.otherNames.append(sender!)
self.resultsNameArray.append(firstname1!)
self.base4String = eventsnapshot.value.objectForKey("img1") as! String
self.resultsImageFiles.append(self.base4String)
}
else //for event creator
{
let firstname2 = eventsnapshot.value.objectForKey("firstname2") as? String
self.otherNames.append(reciever!)
self.resultsNameArray.append(firstname2!)
self.base4String = eventsnapshot.value.objectForKey("img2") as! String
self.resultsImageFiles.append(self.base4String)
}
let newlineChars = NSCharacterSet.newlineCharacterSet()
let evntArray = eventDetails!.componentsSeparatedByCharactersInSet(newlineChars).filter{!$0.isEmpty}
self.eventArray.append(evntArray[0])
self.eventdetailsArray.append(eventAttr!)
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.resultsTable.reloadData()
}
}
})
// self.firebase.removeAuthEventObserverWithHandle(eventhandle)
}
})
//self.messagesRef.removeAuthEventObserverWithHandle(messagehandle)
}
}
})
//firebase.removeAuthEventObserverWithHandle(userhandle)
}
since i am using observeSingleEventOfType i havent coded to remove handlers( i have tried that as well).
In the individual chat, the code is like this:
func refreshResults() {
print("the roomid is \(roomid)")
//update from firebase
let messagehandle = self.messagesRef.childByAppendingPath(roomid).queryOrderedByKey()
.observeEventType(.Value, withBlock: { messageTextsnapshot in
self.messageArray.removeAll()
self.senderArray.removeAll()
// print("the messageTextsnapshot child count is \(messageTextsnapshot.childrenCount)") // I got the expected number of items
let enumerator = messageTextsnapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
let text = rest.value.objectForKey("message") as? String
let sender = rest.value.objectForKey("sender") as? String
if text != nil && text != ""
{
self.messageArray.append(text!)
self.senderArray.append(sender!)
}
}
for subView in self.resultsScrollView.subviews {
subView.removeFromSuperview()
}
for var i = 0; i <= self.messageArray.count-1; i++ {
if self.senderArray[i] == userName {
if (self.messageArray[i].rangeOfString(self.acceptMessage) != nil)
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.myImg, date: NSDate(), type: .AcceptMine)
self.addChatBubble(chatBubbleData)
}
else
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.myImg, date: NSDate(), type: .Mine)
self.addChatBubble(chatBubbleData)
}
} else {
if (self.messageArray[i].rangeOfString(self.acceptMessage) != nil)
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.otherImg, date: NSDate(), type: .Accept)
self.addChatBubble(chatBubbleData)
}
else
{
let chatBubbleData = ChatBubbleData(text: self.messageArray[i], image:self.otherImg, date: NSDate(), type: .Opponent)
self.addChatBubble(chatBubbleData)
}
}
let bottomOffset:CGPoint = CGPointMake(0, self.resultsScrollView.contentSize.height - self.resultsScrollView.bounds.size.height)
self.resultsScrollView.setContentOffset(bottomOffset, animated: false)
}
})
self.messagesRef.removeAuthEventObserverWithHandle(messagehandle)
}
There are a few other listeners similar to this. the problem is when i go back from this view(individual chat to message box, the memory consumption increases. I have cleared all arrays and closed the handlers immediately after use. but still memory consumption increases and sometimes in message box same rows are replicated again. how should i solve this. I tried using
observeSingleEventOfType but it is not a correct solution as the data sync stops.
Used this as reference:
https://www.firebase.com/blog/2015-10-15-best-practices-uiviewcontroller-ios-firebase.html
It looks like your message box object is not being released due to a retain cycle caused by the listener callback block holding a reference to the message box object. You can alleviate this by using [weak self] in blocks that you pass to other objects. For example:
.observeSingleEventOfType(.Value, withBlock:
{
[weak self] roomsnapshot in
let enumerator = roomsnapshot.children
...
This makes 'self' an optional type, and you can then add:
guard let strongSelf = self else { ... }
The problem was that i was closing the listeners on the parent and not on the child. so the listeners were still in memory.
When i closed the listeners on the full path it worked.

Resources