Show Unread Message Counts
PubNub allows you to get a count of messages in channels that were published after a specific timestamp. When users returns to their applications, the app can indicate which channels have new messages and also display counts of unread messages in each channel. You can build this feature easily so your users never miss a message and get the best chat experience.
There are two primary ways you can store a user's last-read timetokens:
- Store timetoken cursors locally within the app
- Store timetoken cursors on PubNub using Objects
Store cursors locally
With the first option, you'll need to store the timetoken of the last message that was read by the user in a channel. If the user uses multiple channels, you'll need to store multiple timetokens, one per channel.
Store cursors using Objects
With the second option, you can store these timetokens inside of PubNub. The Objects feature enables you to persist membership associations between users and channels, and any custom fields that might be useful for your app. Refer to Channel Metadata for more information on channel memberships.
Set channel cursors
Use the setMemberships
method to store timetokens as custom fields on each user-channel membership on PubNub.
pubnub.objects.setMemberships({
channels: [{
id: "channel-1",
custom: {
lastReadTimetoken: 15518041524300120
},
{
id: "channel-2",
custom: {
lastReadTimetoken: 15518041524300251
}
}]
});
let newMembership = PubNubMembershipMetadataBase(
uuidMetadataId: "my_user", channelMetadataId: "channel-1"
)
pubnub.setMemberships(
uuid: newMembership.uuidMetadataId,
channels: [newMembership]
) { result in
switch result {
case let .success(response):
print("The channel memberships for the uuid \(response.memberships)")
if let nextPage = response.next {
print("The next page used for pagination: \(nextPage)")
}
case let .failure(error):
print("Update Memberships request failed with error: \(error.localized### Description)")
}
}
NSArray<NSDictionary *> *channels = @[
@{ @"channel": @"my_channel_1", @"custom": @{ @"lastReadTimetoken": @15518041524300120 },
@{ @"channel": @"my_channel_2", @"custom": @{ @"lastReadTimetoken": @15518041524300251 } }
];
self.client.objects().setMemberships()
.uuid(@"uuid")
.channels(channels)
.includeFields(NMembershipCustomField | PNMembershipChannelField)
.performWithCompletion(^(PNManageMembershipsStatus *status) {
if (!status.isError) {
/**
* UUID's memberships successfully set.
* Result object has following information:
* status.data.memberships - list of UUID's existing memberships,
* status.data.next - cursor bookmark for fetching the next page,
* status.data.prev - cursor bookmark for fetching the previous page,
* status.data.totalCount - total number of UUID's memberships.
*/
} else {
/**
* Handle UUID's memberships set error. Check 'category' property to find out possible
* issue because of which request did fail.
*
* Request can be resent using: [status retry]
*/
}
});
List<PNMembership> setMembershipChannelMetadataIdList = new List<PNMembership>();
if (!string.IsNullOrEmpty(seMembershipChannelMetaId))
{
setMembershipChannelMetadataIdList.Add(new PNMembership() { Channel = "my_channel_1", Custom = new Dictionary<string, object>() { { "lastReadTimetoken", 15518041524300120 } } });
setMembershipChannelMetadataIdList.Add(new PNMembership() { Channel = "my_channel_2", Custom = new Dictionary<string, object>() { { "lastReadTimetoken", 15518041524300251 } } });
}
PNResult<PNMembershipsResult> setMembershipsResponse = await pubnub.SetMemberships()
.Uuid("my-uuid")
.Channels(setMembershipChannelMetadataIdList)
.Include(new PNMembershipField[] { PNMembershipField.CUSTOM, PNMembershipField.CHANNEL, PNMembershipField.CHANNEL_CUSTOM })
.IncludeCount(true)
.ExecuteAsync();
PNMembershipsResult setMembershipsResult = setMembershipsResponse.Result;
PNStatus status = setMembershipsResponse.Status;
Get channel cursors
When the user returns on the app, use the getMemberships
method to fetch channel memberships for the user along with its custom fields. The timetokens can then be passed to the messageCounts
method from above.
pubnub.objects.getMemberships({
include: {
customFields: true
}
});
pubnub.fetchMemberships(uuid: "my_user") { result in
switch result {
case let .success(response):
print("The channel memberships for the uuid \(response.memberships)")
if let nextPage = response.next {
print("The next page used for pagination: \(nextPage)")
}
case let .failure(error):
print("Fetch Memberships request failed with error: \(error.localized### Description)")
}
}
self.client.objects().memberships()
.includeFields(PNMembershipCustomField)
.performWithCompletion(^(PNFetchMembershipsResult *result, PNErrorStatus *status) {
});
PNResult<PNGetMembershipsResult> getMembershipsResponse = await pubnub.GetMemberships()
.Include(new PNMembershipField[] { PNMembershipField.CUSTOM })
.IncludeCount(true)
.Page(new PNPageObject() { Next = "", Prev = "" })
.ExecuteAsync();
PNGetMembershipsResult getMembershipsResult = getMembershipsResponse.Result;
PNStatus status = getMembershipsResponse.Status;
Get counts of unread messages
The messageCounts
method returns a count of unread messages in one or more channels. The count returned is the number of messages in storage with a timetoken value greater than or equal to the passed value in the timetoken
parameter. For more details on retrieving counts of unread messages, refer to Getting Message Counts.
pubnub.messageCounts({
channels: ['ch-1', 'ch-2'],
channelTimetokens: ['15518041524300251'],
}, (status, results) => {
// handle status, response
console.log(status);
console.log(results);
});
pubnub.messageCounts(
channels: [
"ch-1": "15526611838554310",
"ch-2": "15526611838554309"
],
channelTimetokens: ["15518041524300251"]
) { result in
switch result {
case let .success(response):
print("Successful Message Count Response: \(response)")
case let .failure(error):
print("Failed Message Count Response: \(error.localizedDescription)")
}
}
Long lastHourTimetoken = (Calendar.getInstance().getTimeInMillis() - TimeUnit.HOURS.toMillis(1)) * 10000L;
pubnub.messageCounts()
.channels(Arrays.asList("ch-1", "ch-2"))
.channelsTimetoken(Arrays.asList(lastHourTimetoken))
.async(new PNCallback<PNMessageCountResult>() {
@Override
public void onResponse(PNMessageCountResult result, PNStatus status) {
if (!status.isError()) {
for (Map.Entry<String, Long> messageCountEntry : result.getChannels().entrySet()) {
messageCountEntry.getKey(); // the channel name
messageCountEntry.getValue(); // number of messages for that channel
}
} else {
// Handle error accordingly.
status.getErrorData().getThrowable().printStackTrace();
}
}
});
List<string> channelList = new List<string>();
channelList.Add("ch-1");
channelList.Add("ch-2");
pubnub.MessageCounts().Channels(channelList).ChannelsTimetoken(new List<long>{15518041524300251}).Async((result, status) => {
if (!status.Error) {
if((result.Channels != null)){
Debug.Log(string.Format("MessageCounts, {0}", result.Channels.Count));
foreach(KeyValuePair<string, int> kvp in result.Channels){
Debug.Log(string.Format("==kvp.Key {0}, kvp.Value {1} ", kvp.Key, kvp.Value));
}
}
} else {
Debug.Log(status.Error);
Debug.Log(status.ErrorData.Info);
}
});