Showcase Recipes

Read Receipts

Read receipts are hotly debated across chat apps, but their utility is undeniable. This recipe will show you how to implement the infamous ‘blue-tick’ read receipts from WhatsApp, into your own chat app, using Mitter.io timeline events.

Platform Android
Level Advanced
Github View on GitHub
Time Required ~1 hour
Where it matters - Any basic chat app

Prerequisite: Get your basic project setup done by following the steps mentioned over here. Once you’re done implementing a basic chat in your app, follow the steps below.

1 Step 1

First, we need to modify our existing ChatRecyclerViewAdapter to show a checkmark based on the timeline events added to the message being rendered on screen.

Get started by adding an ImageView in your layout for showing current user’s messages, like this:

<android.support.v7.widget.AppCompatImageView
       android:id="@+id/timelineEventIcon"
       android:layout_width="16dp"
       android:layout_height="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintTop_toBottomOf="@id/selfMessageText"
       android:src="@drawable/ic_check_black_24dp"
       android:layout_marginTop="10dp"
       android:layout_marginEnd="15dp"/>

For this layout to work, you need to have 3 different variations of a checkmark icon in your project. One for showing SentTime, one for showing DeliveredTime and another for showing ReadTime. You can add images according to your own choice, in the drawables folder.

2 Step 2

Once that’s done, modify your existing ChatRecyclerViewAdapter to render a checkmark based on timeline events added to your messages.

Accept an extra parameter called channelId in the constructor of your ChatRecyclerViewAdapter which will be used for specifying the channel ID when marking timeline events for messages.

Then, add the following piece of code in the if block of your bindMessage() method to display the checkmark for messages sent by the current user:

val readTimelineEvent = timelineEvents.find {
   it.type == StandardTimelineEventTypeNames.Messages.ReadTime
}

readTimelineEvent?.let {
   itemView?.timelineEventIcon?.setImageResource(R.drawable.ic_done_all_blue_24dp)
   return@with
}

val deliveredTimelineEvent = timelineEvents.find {
   it.type == StandardTimelineEventTypeNames.Messages.DeliveredTime
}

deliveredTimelineEvent?.let {
   itemView?.timelineEventIcon?.setImageResource(R.drawable.ic_done_all_black_24dp)
   return@with
}

itemView?.timelineEventIcon?.setImageResource(R.drawable.ic_check_black_24dp)

Here, we are checking if there’s a timeline event of type ReadTime present in the list of the timeline events for a message. If found, show a blue double checkmark icon for that message and return from the method call so that the icon doesn’t get overridden by any other timeline events present in the list.

If there isn’t any ReadTime timeline event, the method checks for a DeliveredTime event and renders the icon accordingly, or moves on to the next event based on the condition above.

3 Step 3

Now that we have added the provision to show a specific checkmark for timeline events on our messages, it’s time to add some timeline events to incoming messages.

We don’t need to add a SentTime event because that is automatically added by the SDK when a message is sent. However, we do need to mark a message as:

  • Delivered - When the message is received via FCM in the OnPushMessageReceivedCallback
  • Read - When the message is rendered on screen

To mark an incoming message as delivered, add the following code within the onNewMessage() method of your implementation of OnPushMessageReceivedCallback:

if (message.senderId.domainId() != mitter.getUserId()) {
   mitter.Messaging().addDeliveredTimelineEvent(
       channelId = channelId,
       messageIds = listOf(message.messageId)
   )
}

Now, to mark a message as read, start off by posting an event of type MarkRead using the event bus, inside the else block of the bindMessage() method in your ChatRecyclerViewAdapter:

val readTimelineEvent = timelineEvents.find {
   it.type == StandardTimelineEventTypeNames.Messages.ReadTime
}

readTimelineEvent?.let { return@with }

EventBus.getDefault().post(
   MarkRead(
       channelId = channelId,
       messageId = domainId()
   )
)

Here, we’re checking if there’s already a ReadTime timeline event added to the message. If there’s an existing event, we skip sending the event again.

Once that’s done, subscribe to the event in your MainActivity and add a ReadTime timeline event to the message as follows:

@Subscribe(threadMode = ThreadMode.MAIN)
fun onMarkRead(markRead: MarkRead) {
   mitter.Messaging().addReadTimelineEvent(
       channelId = markRead.channelId,
       messageIds = listOf(markRead.messageId)
   )
}

Now run the app on 2 devices/emulators and send message from one user to another. Once the message is sent, close the app on the second emulator (the user who is sending the message) and reopen. You can see the icons show up like this:

4 Step 4

Although, the app shows up the timeline event icons correctly, the change is not instantaneous. Mitter.io delivers a notification via FCM instantly when a new timeline event is added to a message.

We can use this feature to update our UI instantly when a message is delivered/read and make your app seem more responsive.

Inside the onNewMessageTimelineEvent() method in the implementation of OnPushMessageReceivedCallback, post an event of type TimelineEventAdded via the event bus by adding the following code:

EventBus.getDefault().post(
   TimelineEventAdded(messageId, timelineEvent)
)

Similar to Step 2, subscribe to this event in the MainActivity and process the event like this:

@Subscribe(threadMode = ThreadMode.MAIN)
    fun onTimelineEventAdded(timelineEventAdded: TimelineEventAdded) {
        val position = messageList.indexOfFirst {
            it.messageId == timelineEventAdded.messageId
        }

        val timelineEvents = messageList[position].timelineEvents.toMutableList()
        timelineEvents.add(timelineEventAdded.timelineEvent)

        messageList[position] = messageList[position].copy(
            timelineEvents = timelineEvents
        )
        chatRecyclerViewAdapter.notifyItemChanged(position, timelineEvents)
    }

5 Step 5

The only thing that’s left now is to implement an overload of the bindViewHolder() method in our ChatRecyclerViewAdapter to handle a payload(list of timeline events) that we sent in the previous step:

override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
   when {
       payloads.isEmpty() -> holder.bindMessage(messageList[position])
       else -> holder.updateTimelineEvent(payloads[0] as List<TimelineEvent>)
   }
}

Here, if we receive a payload list, we use the holder object to update the timeline event icon, else, we just populate the row data as we would normally.

Finally, we need to add a updateTimelineEvent() method to our ViewHolder class to update the timeline event icon for the item view using a similar logic that we used in Step 1:

fun updateTimelineEvent(timelineEvents: List<TimelineEvent>) {
   val readTimelineEvent = timelineEvents.find {
       it.type == StandardTimelineEventTypeNames.Messages.ReadTime
   }

   readTimelineEvent?.let {
       itemView?.timelineEventIcon?.setImageResource(R.drawable.ic_done_all_blue_24dp)
       return
   }

   val deliveredTimelineEvent = timelineEvents.find {
       it.type == StandardTimelineEventTypeNames.Messages.DeliveredTime
   }

   deliveredTimelineEvent?.let {
       itemView?.timelineEventIcon?.setImageResource(R.drawable.ic_done_all_black_24dp)
       return
   }
}

Final result

That’s all you need to do to implement a delivered/read receipt feature using Mitter.io’s timeline events.
Run the app now and try sending messages from one user to another, and you should see something that looks like this:
View recipe on Github