Badr Bujbara
Badr Bujbara

Reputation: 8691

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

In my chat feature, I show to the current user his chats with other users in a tableview. Each cell on the tableview is the info, avatar and name, of the opposite user. It's easy to store these info in a chat object, but the problem is that the opposite user could have changed his avatar or name at that point.

Hence, I'm fetching the opposite user's avatar and name in the cell configuration of the chat. It's working fine but I'm not sure if there is a better way since I'm not sold on making Firebase network call in each cell.

Here is my cell configuration method:

    func configureCell(from chat: Chat){
    // 1st Get opposite user id
    if let currentUser = Auth.auth().currentUser{
        let user1 = chat.people.first
        let user2 = chat.people.last
        let user1Id = user1?["id"] ?? ""
        let user2Id = user2?["id"] ?? ""
        var oppositeUserId = ""
        if user1Id == currentUser.uid{
            oppositeUserId = user2Id
        }else{
            oppositeUserId = user1Id
        }
        //2nd Fetch opposite user doc
        let oppositeUserDocRef = References.Instance.getUserDocRef(for: oppositeUserId)
        oppositeUserDocRef.getDocument { (oppositeUserDocSnapshot, error) in
            if error != nil{
                print("ERROR FETCHING OPPOSITE USER DOC:: ERROR IS:  \(error?.localizedDescription ?? "")")
                return
            }
            // 3rd get opposite user avatar url and name
            if let otherUserDic = oppositeUserDocSnapshot?.data(){
                if let avatarDic = otherUserDic["avatar"] as? [String: String]{
                    let avatarUrl = avatarDic["url"] ?? ""
                    self.avatarView.sd_setImage(with: URL(string: avatarUrl),
                                          placeholderImage: nil, options: .refreshCached)
                }
                if let name = otherUserDic["name"] as? String{
                    self.uiLabelChatTitle.text = name
                }
            }
        }
    }
}

Upvotes: 0

Views: 118

Answers (2)

Badr Bujbara
Badr Bujbara

Reputation: 8691

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

Upvotes: 0

Vedant
Vedant

Reputation: 339

I have never really used Firebase but for something like this, I would say populate an array of 'chatDetails' objects in a single network request and then configure each reusable cell

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let chat = self.chatDetails[indexPath.row]

        let cell = tableView.dequeueReusableCell(withIdentifier: "identifier") as! ChatDetailsCell

        cell.configureCell(chat: chat)

        return cell

    }

Upvotes: 1

Related Questions