Custom Payloads
Learn how to add actions & extra information to your messages and build a playful and engaging chat experience.
In the previous chapter, we learnt how to send messages to specific people in a group chat with ACLs.
In this chapter, we’ll see how to take the demo app one step further by adding some custom payloads to our outgoing messages.
Schedule meetings on chat
To demonstrate the power and use of custom payloads, we’ll be letting our users schedule meetings on our app. The basic workflow is as follows:
Pick a date while sending a message
Receivers confirm their availability with a Yes/No action
Add the DatePickerDialog
We’ll use the standard DatePickerDialog
to render a calendar dialog on the screen and allow the user to pick a date from the displayed calendar.
Get started by adding a button to the chat screen. For this example, you can use this vector drawable as the calendar button. Just copy this code to your project under the drawable
package:
Now, navigate to your activity_main.xml
and modify the code to look similar to this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/chatRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/inputMessage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorGray"
app:layout_constraintTop_toBottomOf="@id/chatRecyclerView" />
<Button
android:id="@+id/sendButton"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="Send"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/pickDateButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_date_range_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/sendButton"
app:layout_constraintTop_toBottomOf="@id/divider" />
<android.support.v7.widget.AppCompatEditText
android:id="@+id/inputMessage"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginBottom="10dp"
android:layout_marginEnd="10dp"
android:layout_marginStart="20dp"
android:background="@android:color/white"
android:hint="Type your message"
android:inputType="textCapSentences"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/pickDateButton"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
If you compare this file to your previously created layout, you’ll see that we’ve just added an ImageView
showing up the calendar icon that we added in the previous step and adjusted the surrounding views to accommodate this icon.
Lastly, go to your MainActivity
and add the listener for this button to open up the DatePickerDialog
and listen for inputs from the user.
Define an instance variable in your MainActivity
to hold the picked date:
private var pickedDate: String = ""
Then, add this block of code inside your onCreate()
method:
pickDateButton?.setOnClickListener {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
val datePickerDialog = DatePickerDialog(
this,
{ _, year, month, day ->
pickedDate = "$day/${month + 1}/$year"
},
year,
month,
day
)
datePickerDialog.show()
}
Note: To use the
DatePickerDialog
you need to have yourminSdkVersion
set to 24 or above

Send a message with the picked date
Now that we’ve added the functionality to capture a date from the user, we can send it along with our message to be used by our RecyclerView
to render messages differently.
Get started by adding a new data class (or a POJO, if using Java) to hold the picked date:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
data class PickedDate(
@JsonProperty("date") val date: String
)
Now, go to MainActivity
and inside the sendButton.setOnClickListener {}
block (or inside the onClick()
method, if using Java) , modify your code to this:
if (pickedDate.isEmpty()) {
messaging.sendTextMessage(
channelId = channelId,
message = typedInput,
appliedAcls = appliedAcls,
onValueUpdatedCallback = object : Mitter.OnValueUpdatedCallback {
override fun onError(apiError: ApiError) {}
override fun onSuccess() {
inputMessage?.text?.clear()
}
}
)
} else {
val objectMapper = ObjectMapper()
val sender = IdUtils.of<User>(mitter.getUserId())
val pickedDate = PickedDate(this.pickedDate)
val timelineEvent = TimelineEvent(
eventId = UUID.randomUUID().toString(),
type = StandardTimelineEventTypeNames.Messages.SentTime,
eventTimeMs = System.currentTimeMillis(),
subject = sender
)
val message = Message(
messageId = UUID.randomUUID().toString(),
senderId = sender,
textPayload = typedInput,
timelineEvents = listOf(timelineEvent),
appliedAcls = appliedAcls,
messageData = listOf(
MessageDatum(
"io.mitter.android.messages.DateMessage",
objectMapper.valueToTree(pickedDate)
)
)
)
messaging.sendMessage(
channelId = channelId,
message = message,
onValueUpdatedCallback = object : Mitter.OnValueUpdatedCallback {
override fun onError(apiError: ApiError) {
}
override fun onSuccess() {
inputMessage?.text?.clear()
[email protected] = ""
}
}
)
}
What we did here is:
Wrapped our previous
sendTextMessage()
method inside anif-else
blockWrapped the picked date string inside a
PickedDate
object ready to be serialisedConstructed a
SentTime
timeline event for ourMessage
objectConstructed a
Message
object with all the previous params and a newMessageDatum
list
A MessageDatum
is a single unit of your custom payload. It has a unique ID to identify the type of payload you’re sending and also the payload in serialised form.
Now, if you type a message in the input field and tap on the calendar icon you’ll see a date picker pop-up. Select a date and then hit Send.
The app will send out a message with the selected date as its payload.
We’ll make use of this payload in the next section where we’ll show a different chat bubble with this date information.
Show the date message bubbles
To make use of the custom payload that we attached to our message, we need to add some custom layouts for our chat bubbles to accommodate the attached date.
Get started by adding these two layouts to your project:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="@+id/messageContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/back_blue_rounded"
app:layout_constraintEnd_toEndOf="parent">
<android.support.v7.widget.AppCompatTextView
android:id="@+id/selfDateMessageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingBottom="5dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:paddingTop="10dp"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Can we have a meeting?" />
<android.support.v7.widget.AppCompatTextView
android:id="@+id/selfDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="15dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:textColor="#eee"
android:textSize="12sp"
android:textStyle="italic"
app:layout_constraintStart_toStartOf="@id/selfDateMessageText"
app:layout_constraintTop_toBottomOf="@id/selfDateMessageText"
tools:text="On: 26/09/2018" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="@+id/messageContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/back_gray_rounded">
<android.support.v7.widget.AppCompatTextView
android:id="@+id/dateMessageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingBottom="5dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:paddingTop="10dp"
android:textColor="@android:color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Can we have a meeting?" />
<android.support.v7.widget.AppCompatTextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="15dp"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:textSize="12sp"
android:textStyle="italic"
app:layout_constraintStart_toStartOf="@id/dateMessageText"
app:layout_constraintTop_toBottomOf="@id/dateMessageText"
tools:text="On: 26/09/2018" />
</android.support.constraint.ConstraintLayout>
<Button
android:id="@+id/yesButton"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Yes"
app:layout_constraintTop_toBottomOf="@id/messageContainer" />
<Button
android:id="@+id/noButton"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No"
app:layout_constraintStart_toEndOf="@id/yesButton"
app:layout_constraintTop_toBottomOf="@id/messageContainer" />
</android.support.constraint.ConstraintLayout>
After you’ve added these, the next step is to modify your ChatRecyclerViewAdapter
to something like this:
class ChatRecyclerViewAdapter(
private val messageList: List<Message>,
private val currentUserId: String
) : RecyclerView.Adapter<ChatRecyclerViewAdapter.ViewHolder>() {
private val objectMapper = ObjectMapper()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(getLayoutId(viewType), parent, false)
return ViewHolder(itemView)
}
override fun getItemCount(): Int = messageList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindMessage(messageList[position], holder.itemViewType)
}
override fun getItemViewType(position: Int): Int = when (messageList[position].senderId.domainId()) {
currentUserId -> {
val message = messageList[position]
if (message.messageData.isEmpty()) {
MessageTypes.SELF_PLAIN_MESSAGE
} else {
MessageTypes.SELF_DATE_MESSAGE
}
}
else -> {
val message = messageList[position]
if (message.messageData.isEmpty()) {
MessageTypes.OTHER_PLAIN_MESSAGE
} else {
MessageTypes.OTHER_DATE_MESSAGE
}
}
}
object MessageTypes {
const val SELF_PLAIN_MESSAGE = 0
const val OTHER_PLAIN_MESSAGE = 1
const val SELF_DATE_MESSAGE = 2
const val OTHER_DATE_MESSAGE = 3
}
private fun getLayoutId(viewType: Int): Int = when (viewType) {
MessageTypes.SELF_PLAIN_MESSAGE -> R.layout.item_message_self
MessageTypes.SELF_DATE_MESSAGE -> R.layout.item_message_date_self
MessageTypes.OTHER_DATE_MESSAGE -> R.layout.item_message_date
else -> R.layout.item_message_other
}
inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
fun bindMessage(message: Message, viewType: Int) {
with(message) {
when (viewType) {
MessageTypes.SELF_PLAIN_MESSAGE -> {
itemView?.selfMessageText?.text = textPayload
}
MessageTypes.OTHER_PLAIN_MESSAGE -> {
itemView?.otherMessageText?.text = textPayload
}
MessageTypes.SELF_DATE_MESSAGE -> {
val pickedDate = objectMapper.treeToValue(messageData[0].data, PickedDate::class.java)
itemView?.selfDateMessageText?.text = textPayload
itemView?.selfDate?.text = "On: ${pickedDate.date}"
}
MessageTypes.OTHER_DATE_MESSAGE -> {
val pickedDate = objectMapper.treeToValue(messageData[0].data, PickedDate::class.java)
itemView?.dateMessageText?.text = textPayload
itemView?.date?.text = "On: ${pickedDate.date}"
}
}
}
}
}
}
Here, we did a couple of things:
Added our new date message layouts
Mapped them to their specific message types
Rendered each message based on their message type or rather view type
If you run the app now and send a message with a date, you’ll see the date appearing right below your typed message text. Also, your date messages will appear different for different users:
Just the message text and the date for self-messages
Additional Yes/No buttons for other users’ messages
Go ahead, try this out.

Add action to the chat bubble buttons
The only thing that’s left in this chapter is to add some actions to the buttons that we added in the previous step.
First, create two empty classes called YesAction
and NoAction
. These are basically events that we’ll be sending out on the button clicks and act on them when we receive them in the MainActivity
.
After you’ve done that, navigate to the ChatRecyclerViewAdapter
and modify the following block inside your ViewHolder
class like this:
MessageTypes.OTHER_DATE_MESSAGE -> {
val pickedDate = objectMapper.treeToValue(messageData[0].data, PickedDate::class.java)
itemView?.dateMessageText?.text = textPayload
itemView?.date?.text = "On: ${pickedDate.date}"
itemView?.yesButton?.setOnClickListener {
EventBus.getDefault().post(YesAction())
}
itemView?.noButton?.setOnClickListener {
EventBus.getDefault().post(NoAction())
}
}
Here, we’re basically sending out events using the event bus that we previously added to our app, on each button click.
Now, we need to subscribe to these sent events and act on them in our MainActivity
. Navigate to your MainActivity
and add the following methods:
@Subscribe(threadMode = ThreadMode.MAIN)
fun onYesAction(yesAction: YesAction) {
messaging.sendTextMessage(
channelId = channelId,
message = "Yes, I'm available"
)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onNoAction(noAction: NoAction) {
messaging.sendTextMessage(
channelId = channelId,
message = "No, I'm not available"
)
}
What we’re doing here is listening to the incoming YesAction
and NoAction
events and sending out a plain text message accordingly.
This will act as a shortcut confirmation message action for our users, kind of like what you see in popular apps like Google Allo and Facebook Messenger.

And that’s the end of the chapter. You’ve successfully added custom payloads to your messages and responded to them accordingly to update your UI.
Last updated