Display last message in chat with firestore

How I can get and display last message in my chat?

For test, I created four users with test messages. Now I can display only last message for all users. I mark red color.

Also I use firebase to save messages and create channels.

Struct in firebase look like this:

- Chats 
    - channel id 
        - document data (then be stored ID and NAME of channel)
        - collection thread
            - documents data (then be stored MESSAGES)

My struct in channel:

struct Channel {
    let id: String?
    let name: String

    init(name: String) {
        id = nil
        self.name = name
    }

    init?(document: DocumentSnapshot) {
        let data = document.data()!
        guard let name = data["name"] as? String else {
            return nil
        id = document.documentID
        self.name = name
    }
}

extension Channel: DatabaseRepresentation {
    var representation: [String : Any] {
        var rep = ["name": name]
        if let id = id {
            rep["id"] = id
        }
        return rep
    }
}

And my struct message, I use MessageKit:

struct Message: MessageType {

let id: String?
let content: String
let sentDate: Date
let sender: SenderType

var kind: MessageKind {
    if let image = image {
        return .photo(ImageMediaItem.init(image: image))
    } else {
        return .text(content)
    }
}

var messageId: String {
    return id ?? UUID().uuidString
}

var image: UIImage? = nil
var downloadURL: URL? = nil

init(profile: Profile, content: String) {
    sender = Sender(id: profile.id, displayName: profile.name)
    self.content = content
    sentDate = Date()
    id = nil
}

init?(document: QueryDocumentSnapshot) {
    let data = document.data()

    guard let sentDate = (data["created"] as? Timestamp)?.dateValue() else { 
        return nil
    }

    guard let senderID = data["senderID"] as? String else {
        return nil
    }
    guard let senderName = data["senderName"] as? String else {
        return nil
    }

    id = document.documentID

    self.sentDate = sentDate
    sender = Sender(id: senderID, displayName: senderName)

    if let content = data["content"] as? String {
        self.content = content
        downloadURL = nil
    } else if let urlString = data["url"] as? String, let url = URL(string: urlString) {
        downloadURL = url
        content = ""
    } else {
        return nil
    }
}

}

extension Message: DatabaseRepresentation {

var representation: [String : Any] {
    var rep: [String : Any] = [
        "created": sentDate,
        "senderID": sender.senderId,
        "senderName": sender.displayName 
    ]

    if let url = downloadURL {
        rep["url"] = url.absoluteString
    } else {
        rep["content"] = content
    }

    return rep
}

}

For load my chennels I use code below:

fileprivate func observeQuery() {
    guard let query = query else { return }
    listener = query.addSnapshotListener { (snapshot, error) in
        guard let snapshot = snapshot else {
            print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
            return
        }

        snapshot.documentChanges.forEach { (change) in
            self.handleDocumentChange(change)
        }
    }
}

private func handleDocumentChange(_ change: DocumentChange) {
    guard let channel = Channel(document: change.document) else {
        return
    }
    switch change.type {
    case .added:
        addChannelToTable(channel)
    case .modified:
        updateChannelInTable(channel)
    case .removed:
        removeChannelFromTable(channel)
    }
}

private func addChannelToTable(_ channel: Channel) {
    guard !channels.contains(channel) else {
        return
    }
    channels.append(channel)
    channels.sort()

    guard let index = channels.index(of: channel) else {
        return
    }
    tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}

private func updateChannelInTable(_ channel: Channel) {
    guard let index = channels.index(of: channel) else {
        return
    }
    channels[index] = channel
    tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}

private func removeChannelFromTable(_ channel: Channel) {
    guard let index = channels.index(of: channel) else {
        return
    }
    channels.remove(at: index)
    tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}

I think need update my Channel struct. But how to do it?

And how to correct load and display last message from firebase?

If need more info pls tell me, I will update my question.

enter image description here

Upvotes: 0

Views: 3246

Answers (1)

Jay
Jay

Reputation: 35658

If the question is how to get only the last message from Firestore, you need to define how to determine what the last message is. That's usually done via a timestamp - the latest timestamp will be the last message.

The structure in the question is a little unclear so let me provide a simple example.

messages //collection
   document_0 //documentID auto-generated
      msg: "Last Message"
      timestamp: "20191201"
   document_1
      msg: "First message"
      timestamp: "20190801"
   document_2
      msg: "A message in the middle"
      timestamp: "20191001"

As you can see, no matter what order they are written to Firestore, it's clear that the one with the latest timestamp (20191201 ) is the last message.

To get the last message we need a query that does two things:

1) Query the messages node, sort descending, which will put the last message 'at the top'

2) Limit the query to 1, which will get that message.

func readLastMessage() {
    let ref = Firestore.firestore().collection("messages").order(by: "timestamp", descending: true).limit(to: 1)
    ref.getDocuments(completion: { querySnapshot, error in
        guard let snapshot = querySnapshot else {
            print("Error fetching snapshots: \(error!)")
            return
        }

        if let doc = snapshot.documents.first {
            let docID = doc.documentID
            let msg = doc.get("msg")
            print(docID, msg)
        }
    })
}

and the output

Last Message

The above code gets the last message but could be expanded upon by adding an observer instead of getDocuments in the same fashion that will notify the app when there's a new last message.

Upvotes: 1

Related Questions