When to Detach Firebase Listeners in Tableview Cell

When to detach firebase listeners in tableView cell?

As Jay suggested, I am attaching .childChanged observer on each chat newly downloaded.

However, if I go to firebase console and update the value of a child at ref path, childChanged observer is not always triggered. Sometimes, it works, sometimes it does not What could be the problem?

I am using break points on all lines and none of them is hit when value is changed in database.

example: name:"Alex"

change with: name: "John"

UPDATE
This answer is correct, I had forgotten to delete my previous implementation in which was detaching the listeners in prepareForReuse()

var currentUserChats = [Chat]() {
didSet(newValue){
attachChildChangedObserverOn(chat: newValue)
}
}

var observersArray = [String: UInt]() // chatUID:handle

func attachChildChangedObserverOn(chat: Chat) {

var handle: UInt = 0
let ref = DDatabaseRReference.chats.reference().child(chat.chatUID).child("users").child(currentUser.userUID)
handle = ref.observe(.childChanged, with: {[weak self] (snapshot) in

self?.observersArray[chat.chatUID] = handle

print("snapshot.value is \(snapshot.value) and snapKey is \(snapshot.key)")
guard snapshot.exists() else {return}

let chatChanged = chat

var lastMessage = ""
var unreadMessagesCount = 0
var lastUpdate = 0.0

switch snapshot.key {
//case....
}

})

}

Adding and removing listeners continually

From what you're describing, there will be no negative impact on performance and cost. Adding and removing listeners is cheap. If you are only doing it during page navigation, that is not actually very fast at all, by any standard.

A Better Way To Handle Retrieving Data from Firebase in a TableView Cell

As they suggested in the comments, prefetching the data would be a better solution. It's tricky and depends on each app's details. My goal was to mimic WhatsApp's chat list functionality updating users info in realtime.

Here is what I did to achieve this:

    func showChats(){
guard let currentUser = Auth.auth().currentUser else{
return
}
let profileController = UserProfileController.instance
if let snapshot = profileController.chats{
for document in snapshot.documents{
let chat = ChatController.instance.getChat(from: document)
chats.append(chat)
print("GOT CHAT:::: \(document.data())")
}
tableview.reloadData()
}
else{
print("NOTHING IN INBOX!!!!")
}
// Attach listeners to chat users docs to listen for change in avatars and names
// 1st: Loop through each chat to get people and know opposite user id
for (i, var chat) in chats.enumerated(){
let person1 = chat.people.first
let person2 = chat.people.last
let person1Id = person1?["id"] ?? ""
let person2Id = person2?["id"] ?? ""
var oppositeUserId = ""
var opposteUsrIsPerson1 = false
if person1Id == currentUser.uid{
oppositeUserId = person2Id
opposteUsrIsPerson1 = false
}
else{
oppositeUserId = person1Id
opposteUsrIsPerson1 = true
}
// 2nd: Fetch opposite user doc and add listener to it
let oppositeUserDocRef = References.Instance.getUserDocRef(for: oppositeUserId)
let docListener = oppositeUserDocRef.addSnapshotListener { (oppositeUserDocSnapshot, error) in
if error != nil {
print("ERROR FETCHING OTHER PERSON DOC:: ERROR IS: \(error?.localizedDescription ?? "")")
return
}
// 3rd: get other user avatar url and name from his doc
if let oppositeUserDic = oppositeUserDocSnapshot?.data(){
var avatarUrl = ""
var name = ""
if let avatarDic = oppositeUserDic["avatar"] as? [String: String]{
avatarUrl = avatarDic["url"] ?? ""
}
if let oppositeUsrName = oppositeUserDic["name"] as? String{
name = oppositeUsrName
}
// 4th: Create ChatPerson object with the fetched values and replace the existing one
let chatPerson = ChatPerson(id: oppositeUserId, name: name, avatarUrl: avatarUrl)
if opposteUsrIsPerson1{
chat.people[0] = chatPerson.toDictionalry()
}
else{
chat.people[1] = chatPerson.toDictionalry()
}
// 5th: Update data and reload the chat row
self.chats[i] = chat
let indexpath = IndexPath(row: i, section: 0)
self.tableview.reloadRows(at: [indexpath], with: .automatic)
}
}
//6th: Add doc listener to liteners list to remove it later in deinit()
chatUserListeners.append(docListener)
}
}

In the view controller's deinit() loop through the listeners to remove them:

    deinit {
for listener in chatUserListeners{
listener.remove()
}
}

Can I order firebase addValueEventListeners so that they execute in a specific order?

 .addValueEventListener()

when the database updates it does something that triggers this function to run .So lets say you have a copy of the Firebase data on the device
and everything is sync correctly then NOTHING happens (you dont iven use the internet connection to get data u just ask the db if it did any modifications or no) ,Now all the data you get from you db is from your local db version of your Firebase db.
this is just details aboute how firebase works systhematicaly.

If data gets inserted you get the modifications that happened only + the data that didn't got changed in your "local Firebase db" .


So now you need to store data in a List by clearing and refilling it when a modification happens on "remote Firebase db".

And then work on the Lists you have on your device .

Handling asynchronous Firestore data reading in tableView - iOS

As @Galo Torres Sevilla mentioned, addSnapshotListener method is async and you need to add completion handler to your getDataFromDatabase() function.

Make following changes in your code:

  1. Declare Global variable for notes.

    var list_notes = [String]()
  2. Add completion handler to getDataFromDatabase() method.

    func getDataFromDatabase(callback: @escaping([String]) -> Void) {
    var notes: [String] = []
    collectionRef = Firestore.firestore().collection("Notes")

    collectionRef.addSnapshotListener { querySnapshot, error in
    guard let documents = querySnapshot?.documents else {
    print("Error fetching documents: \(error!)")
    return
    }
    notes = documents.map { $0["text"]! } as! [String] // text is a field saved in document
    print("inside notes: \(notes)")

    callback(notes)
    }
    }
  3. Lastly, call function on appropriate location where you want to fetch notes and assign retrieved notes to your global variable and reload TableView like below:

    self.getDataFromDatabase { (list_notes) in
    self.list_notes = list_notes
    DispatchQueue.main.async {
    self.tableView.reloadData()
    }
    }

Changes in TableView:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("tableview numberOfRowsInSection called")
return self.list_notes.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("Cells")
let cell = tableView.dequeueReusableCell(withIdentifier: "firstCell", for: indexPath)

cell.textLabel!.text = String(self.list_notes[indexPath.row].prefix(30))

return cell
}


Related Topics



Leave a reply



Submit