Let’s take this tutorial even further by building a simple chat UI and using Mitter.io to send/receive messages.
Build the basic layouts
Before we start handling data, we need a UI to show the outgoing/incoming data. Get started by adding some layouts for your chat bubbles and the chat RecyclerView.
To speed up this tutorial, just copy and paste the following pieces of code to your project. This is very basic Android stuff and we’ll not be diving deep into this part.
So, you’ve added some basic layouts for rendering the chat bubbles. Now, let’s style the chat window. Open up your activity_main.xml layout file and add the following code:
Now that your layouts are in place, get started by adding an adapter for your RecyclerView.
Assuming you’ve named your adapter ChatRecyclerViewAdapter, your class should look something like this:
ChatRecyclerViewAdapter.kt
class ChatRecyclerViewAdapter(
private val messageList: List<Message>,
private val currentUserId: String
) : RecyclerView.Adapter<ChatRecyclerViewAdapter.ViewHolder>() {
private val MESSAGE_SELF_VIEW = 0
private val MESSAGE_OTHER_VIEW = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutId = if (viewType == MESSAGE_SELF_VIEW) R.layout.item_message_self else R.layout.item_message_other
val itemView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
return ViewHolder(itemView)
}
override fun getItemCount(): Int = messageList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindMessage(messageList[position])
}
override fun getItemViewType(position: Int) = if (messageList[position].senderId.domainId() == currentUserId)
MESSAGE_SELF_VIEW else MESSAGE_OTHER_VIEW
inner class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
fun bindMessage(message: Message) {
with(message) {
if (senderId.domainId() == currentUserId) {
itemView?.selfMessageText?.text = textPayload
} else {
itemView?.otherMessageText?.text = textPayload
}
}
}
}
}
ChatRecyclerViewAdapter.java
public class ChatRecyclerViewAdapter extends RecyclerView.Adapter<ChatRecyclerViewAdapter.ViewHolder> {
private List<Message> messageList;
private String currentUserId;
private int MESSAGE_SELF_VIEW = 0;
private int MESSAGE_OTHER_VIEW = 1;
public ChatRecyclerViewAdapter(
List<Message> messageList,
String currentUserId
) {
this.messageList = messageList;
this.currentUserId = currentUserId;
}
@NonNull
@Override
public ChatRecyclerViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
int layoutId;
if (viewType == MESSAGE_SELF_VIEW) {
layoutId = R.layout.item_message_self;
} else {
layoutId = R.layout.item_message_other;
}
View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ChatRecyclerViewAdapter.ViewHolder holder, int position) {
Message message = messageList.get(position);
if (message.getSenderId().domainId().equals(currentUserId)) {
holder.selfMessageText.setText(message.getTextPayload());
} else {
holder.otherMessageText.setText(message.getTextPayload());
}
}
@Override
public int getItemCount() {
return messageList != null ? messageList.size() : 0;
}
@Override
public int getItemViewType(int position) {
if (messageList.get(position).getSenderId().domainId().equals(currentUserId)) {
return MESSAGE_SELF_VIEW;
} else {
return MESSAGE_OTHER_VIEW;
}
}
class ViewHolder extends RecyclerView.ViewHolder {
private TextView selfMessageText;
private TextView otherMessageText;
public ViewHolder(View itemView) {
super(itemView);
selfMessageText = (TextView) itemView.findViewById(R.id.selfMessageText);
otherMessageText = (TextView) itemView.findViewById(R.id.otherMessageText);
}
}
}
Here, we receive a list of Message objects to display the chat items, and compare the senderId field to the currentUserId field to decide whether the bubble should render on the right or on the left.
Render messages in the window
The final step to rendering the messages in a channel to the screen is to wire up the ChatRecyclerViewAdapter that you created to the RecyclerView that you had already added to your layout.
Get started by initialising an empty list for holding the incoming Message objects and setting a layout manager for your RecyclerView.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var mitter: Mitter
private val channelId: String = "6dedfac5-8060-4ad3-8d69-20a72fb86899"
private val messageList = mutableListOf<Message>()
private lateinit var chatRecyclerViewAdapter: ChatRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mitter = (application as MyApp).mitter
val users = mitter.Users()
val channels = mitter.Channels()
val messaging = mitter.Messaging()
chatRecyclerView.layoutManager = LinearLayoutManager(this)
}
}
Now, you need to make a call to fetch messages in the channel that you had already created.
If you’ve noticed, in the previous code snippet, the channel ID is already defined in the class. Replace the channel ID string with your actual channel ID over there.
Once that’s done, just make a call to the getMessagesInChannel() method on the Messaging object to get a list of messages in the channel:
messaging.getMessagesInChannel(
channelId,
new FetchMessageConfig(),
new Mitter.OnValueAvailableCallback<List<Message>>() {
@Override
public void onValueAvailable(List<Message> messages) {
messageList.addAll(messages);
chatRecyclerViewAdapter = new ChatRecyclerViewAdapter(
messageList,
mitter.getUserId()
);
chatRecyclerView.setAdapter(chatRecyclerViewAdapter);
}
@Override
public void onError(ApiError apiError) {
Log.d("MSA", "Error in getting messages");
}
}
);
Here, we add the total incoming message list to our already defined empty message list. Next, we hook up that list with our ChatRecyclerViewAdapter and also retrieve the currently logged-in user ID and pass it to the adapter for the bubble alignment that we discussed previously.
The final step is to assign our ChatRecyclerViewAdapter object as the RecyclerView adapter to render the elements on the screen.
If you open up your app right now, it’ll display a blank screen because you haven’t sent any messages yet.
Let’s do that now.
Send a basic text message
We’ve already added a basic EditText and a Button to our app while defining the layouts. Let’s connect them in the activity to send the typed text on the press of the Send button.
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
messaging.sendTextMessage(
channelId,
inputMessage.getText().toString(),
new AppliedAclList(
new ArrayList<AppliedAcl>(),
new ArrayList<AppliedAcl>()
),
new Mitter.OnValueUpdatedCallback() {
@Override
public void onSuccess() {
inputMessage.getText().clear();
}
@Override
public void onError(ApiError apiError) { }
}
);
}
});
Here, we’re listening to the click events on the Send button and sending a message by calling the sendTextMessage() method on the Messaging object. We pass the typed text to the method as the message text.
After the message has been sent successfully, we clear the input area. You can also show a progress bar while sending the message by utilising this callback.
That’s all wired in. You can test if this works by typing something in the input field and hitting Send.
Do note that you won’t see the message appear on the screen even after it’s sent successfully. This is because we haven’t hooked our push message listener with the UI.
We’ll do this in the next section. For now, after sending a message, just close and reopen the app to see the message populated on the screen.
Hook push messages with the UI
You can choose any way you want to pass around data from your Application class to your MainActivity. For this tutorial, we’ll stick to an event bus to do our job.
We’ll use Greenrobot’s EventBus 3 for this project. It’s pretty easy to use, while being reliable. Add the library to your project by including this line in your build.gradle:
build.gradle
implementation 'org.greenrobot:eventbus:3.1.1'
Once that’s done, add a subscribing method in your MainActivity to listen to the incoming messages: