Edit 2
This is my current setup. I am removing the observer when the side navigation view hides and restart it when it appears. This would be unnecessary if user defaults would return a fresh value in queryStarting(atValue: defaults.double(forKey: lastReadMessageTimestampKey) + 0.000001)
like timeIntervalSince1970
does in a similar query elsewhere: queryStarting(atValue: NSDate().timeIntervalSince1970)
.
class SideNavigationController: UIViewController {
...
var unreadMessageCountUpdateQueryHandle: DatabaseHandle?
...
override func viewWillAppear(_ animated: Bool) {
logger.info("SideNavigationControllers view will appear")
updateUnreadMessagesCount()
super.viewWillAppear(true)
...
}
override func viewWillDisappear(_ animated: Bool) {
logger.info("SideNavigationControllers view will disapear")
stopUpdatingUnreadMessagesCount()
super.viewWillDisappear(animated)
...
}
func updateUnreadMessagesCount() {
logger.info("Updating count of unread messages")
let messageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue
let defaults = UserDefaults.standard
unreadMessageCountUpdateQueryHandle = messagesReference.queryOrdered(byChild: messageTimestampKey).queryStarting(atValue: defaults.double(forKey: lastReadMessageTimestampKey) + 0.000001).observe(.value) { (snapshot) in
if let value = snapshot.value as? [String: Any] {
print("Number of unread messages: \(value.count) Last Read TS \(UserDefaults.standard.double(forKey: lastReadMessageTimestampKey))")
self.menuItems[1].itemName = "Discussions (\(value.count))"
self.sideNavTableView.reloadData()
} else {
self.menuItems[1].itemName = "Discussions"
self.sideNavTableView.reloadData()
}
}
}
func stopUpdatingUnreadMessagesCount() {
guard let handle = unreadMessageCountUpdateQueryHandle else { return }
messagesReference.removeObserver(withHandle: handle)
}
Edit:
Adding .synchronize()
after saving the default value is not helping either.
I have also tweaked the query a little bit to start with an offset from the last read message's timestamp so that it won't be included when I count unread messages.
Now the query looks like this:
let discussionMessageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue
messagesReference.queryOrdered(byChild: discussionMessageTimestampKey)
.queryStarting(atValue: UserDefaults.standard.double(forKey: lastReadMessageTimestampKey) + 0.000001)
.observe(.value) { (snapshot) in
if let value = snapshot.value as? [String: Any] {
print("Number of unread messages: \(value.count) Last Read TS \(UserDefaults.standard.double(forKey: lastReadMessageTimestampKey))")
}
}
With this query initially the unread messages count is 0
and then it becomes 1, 2, 3, etc. as new messages come in. This is even after I open the chat view and the last read message timestamp gets updated inside the query. I know that the last read message timestamp is updating as the console output looks like this:
Number of unread messages: 1 Last Read TS TS_OF_SECOND_LAST_MESSAGE
Number of unread messages: 2 Last Read TS TS_OF_SECOND_LAST_MESSAGE
Number of unread messages: 3 Last Read TS TS_OF_SECOND_LAST_MESSAGE
And my database structure looks like this for people who are curious:
Another way to do this might involve including 2 queries. Then the first query's execution could be stopped when the view is disappearing. But I don't know how to stop a query execution and could not find any documentation for it.
let messageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue
let defaults = UserDefaults.standard
let query = messagesReference.observe(.childAdded) { (snapshot) in
if snapshot.exists() {
let startTimestamp = defaults.double(forKey: lastReadMessageTimestampKey)
let offset: Double = 0.00000001
messagesReference.queryOrdered(byChild: messageTimestampKey).queryStarting(atValue: startTimestamp + offset).observe(.value) { (snapshot) in
if let value = snapshot.value as? [String: Any] {
print("Number of unread messages: \(value.count) Last Read TS \(UserDefaults.standard.double(forKey: lastReadMessageTimestampKey))")
}
}
}
}
I am creating a chat app and I want to count the number of unread messages. The value of the last read timestamp is saved at a location in my realtime database and each message has a timestamp attached to it. The last read message's timestamp is also stored in user defaults. So I tried using that value with queryStarting(atValue:)
. But it is not working as expected. Even after the default's value gets updated, the query still uses an older value, and unread message count keeps going up with .value
event. With .childAdded
event, I keep getting a constant (always incorrect) count even when new messages are added.
let discussionMessageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue
messagesReference.queryOrdered(byChild: discussionMessageTimestampKey).queryStarting(atValue: UserDefaults.standard.double(forKey: lastReadMessageTimestampKey)).observe(.value) { (snapshot) in
if let value = snapshot.value as? [String: Any] {
print("Number of unread messages: \(value.count) Last Read TS \(UserDefaults.standard.double(forKey: lastReadMessageTimestampKey))")
}
}
I am using a similar query for firing .childAdded
event only for newly added messages and not deleted messages. Here I have a strong feeling that NSDate().timeIntervalSince1970
keeps returning a fresh value whenever a child is added or deleted. As even if 4 messages are added after the query was created and 1 of them was deleted, this event is not triggered. As it is designed to be triggered as explained by Frank here.
let discussionMessageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue
messagesReference.queryOrdered(byChild: discussionMessageTimestampKey).queryStarting(atValue: NSDate().timeIntervalSince1970).observe(.childAdded) {
(snapshot) in
...
}
This brings me to my question. Since the user defaults query is not working, and the last read message timestamp is also stored in firebase. Can we maybe use that value as queryStarting(atValue:)
instead (as I am hoping it will return a fresh value like NSDate().timeIntervalSince1970
does). The problem here is that both the last read message timestamp and the number of messages can change. Hence if I create an observer on one, then it will lead to unnecessary data loading at another end. Could someone explain to me why the query works as expected with NSDate().timeIntervalSince1970
but not with UserDefaults
? And how I can fix this issue of counting number of unread messages optimally.
(One method I thought of involved adding a KVO on user defaults. But I saw that process also has complications so I did not go down that route.)