Using the UI Framework

Learn how to use Mitter.io UI Framework to get a chat app running in record time.

The Mitter.io Core package provides easy access to the platform from your Android project. However, getting from zero to seeing chat messages appear in your app requires a bit of UI setup which is mostly boilerplate code.

The UI framework package aims to reduce the time taken to get a basic chat app running to the minimum by providing a lightweight framework using which you can add your custom UI elements like chat bubbles and more to your app without filling out too much boilerplate.

Installation

Adding the UI framework package is similar to adding any library in Android. Just add the following line to your build.gradle file and sync your dependencies:

build.gradle
implementation 'io.mitter.android:uiframework:0.1.5'

Adding a ChannelWindow

Currently, the UI framework provides a ChannelWindow view which is a very thin wrapper on Android’s RecyclerView. The difference between RecyclerView and ChannelWindow is that the latter provides additional features like pagination out of the box.

You can add a ChannelWindow view to your Activity or Fragment XML layout where you want the messages to be displayed.

activity_chat.xml
<io.mitter.android.ui.views.ChannelWindow
android:id="@+id/channelWindow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />

Creating producers

A ChannelWindow needs to be connected to a ChannelWindowManager to work. A ChannelWindowManager creates an internal RecyclerView adapter from a list of producers and displays your messages on the ChannelWindow.

What are producers?

Producers are just simple view producers. You use a producer to define and create a single row of your channel list. In simple terms, this is where you control the look and feel of your message bubbles.

Define a producer

To define a new producer, you need to implement the ChannelWindowElementProducer interface. This interface has 3 methods:

  • canProduceFor() - Here, you need to write a condition which will match this view for a message type

  • getViewId() - Here, you need to specify a layout ID which you want your message to be rendered on

  • produceView() - This is where you get a RecyclerView.ViewHolder and your Message. You need to bind your message data to the UI using the supplied ViewHolder

A simple text message producer would look similar to the one below:

Kotlin
Java
TextMessageProducer.kt
class TextMessageProducer(
val mitter: Mitter
) : ChannelWindowElementProducer {
override fun canProduceFor(element: Any): Boolean {
return element is Message && element.payloadType == StandardPayloadTypeNames.TextMessage
}
override fun getViewId(): Int = R.layout.item_text_message
override fun produceView(holder: RecyclerView.ViewHolder, element: Any) {
val message = element as Message
holder.itemView.text.text = message.textPayload
holder.itemView.senderName.text = message.senderId.domainId()
holder.itemView.textMessageWrapper.gravity = when (mitter.getUserId()) {
message.senderId.domainId() -> Gravity.END
else -> Gravity.START
}
}
}
TextMessageProducer.java
public class TextMessageProducer implements ChannelWindowElementProducer {
private Mitter mitter;
public TextMessageProducer(Mitter mitter) {
this.mitter = mitter;
}
@Override
public void produceView(@NotNull RecyclerView.ViewHolder holder, @NotNull Object o) {
Message message = (Message) o;
TextView messageText = holder.itemView.findViewById(R.id.text);
TextView senderNameText = holder.itemView.findViewById(R.id.senderName);
LinearLayout textMessageWrapperLayout = holder.itemView.findViewById(R.id.textMessageWrapper);
messageText.setText(message.getTextPayload());
senderNameText.setText(message.getSenderId().domainId());
if (mitter.getUserId().equals(message.getSenderId().domainId())) {
textMessageWrapperLayout.setGravity(Gravity.END);
} else {
textMessageWrapperLayout.setGravity(Gravity.START);
}
}
@Override
public boolean canProduceFor(@NotNull Object o) {
return o instanceof Message && ((Message) o).getPayloadType().equals(StandardPayloadTypeNames.TextMessage);
}
@Override
public int getViewId() {
return R.layout.item_text_message;
}
}

Writing a producer is quite similar to writing a RecyclerView adapter. A producer, however, abstracts away boilerplate code and helps you write smaller and separated view binding logic.

You can create as many producers as you want depending on your incoming message types. Some common examples would be:

  • TextMessageProducer

  • ImageMessageProducer

  • EmojiMessageProducer

And so on.

Setting up a manager

As already discussed in the previous section, a ChannelWindow needs a ChannelWindowManager to work. A ChannelWindowManager in turn needs one or more producers to function.

Now that you’ve already created a producer, you can setup your manager by passing your producer and a channel ID for which you want the messages to be loaded on the app screen.

Before you can initialise a manager, you need to have an instance of MitterUi. You can define an instance of MitterUi inside your activity where you want to display your message list (or ChannelWindow).

Kotlin
Java
ChatActivity.kt
lateinit var mitterUi: MitterUi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_channel_window_test)
mitterUi = MitterUi(
(application as MessageApp).mitter
)
}
ChatActivity.java
private MitterUi mitterUi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mitterUi = new MitterUi(
((MessageApp) getApplication()).mitter
);
}

Here MessageApp refers to the global Application class for this project where the Mitter instance is defined. Change this to wherever you’ve defined your Mitter instance in your project, as shown in the previous articles of this guide.

Once you have that, you can easily get an instance of ChannelWindowManager as follows:

Kotlin
Java
ChatActivity.kt
val channelWindowManager = ChannelWindowManager(
mitterUi,
"channel-id",
ChannelWindowConfig(producers = listOf(
TextMessageProducer(mitterUi.mitter),
StringProducer()
)
)
ChatActivity.java
List<ChannelWindowElementProducer> producers = new ArrayList<>();
producers.add(new TextMessageProducer(mitterUi.getMitter()));
PaginationConfig paginationConfig = new PaginationConfig();
ChannelWindowManager channelWindowManager = new ChannelWindowManager(
mitterUi,
"channel-id",
new ChannelWindowConfig(
producers,
paginationConfig
)
);

After that, you just need to hook your manager to your ChannelWindow view:

Kotlin
Java
ChatActivity.kt
channelWindowManager.attach(channelWindow)
ChatActivity.java
ChannelWindow channelWindow = findViewById(R.id.channelWindow);
channelWindowManager.attach(channelWindow);

If you now run your app, you should be able to get messages populated on your device’s screen for the channel ID you mentioned over here. This assumes that you have properly setup Mitter with your application credentials as shown in the previous articles.

Paginating messages

By default, the ChannelWindow will load 10 messages at a single go in the specified channel every time you scroll up. You can change this behaviour and also attach a callback to get notified when pagination happens by using the PaginationConfig parameter in the ChannelWindowManager.

Get started by defining your PaginationConfig first:

Kotlin
Java
ChatActivity.kt
val paginationConfig = PaginationConfig(
itemsPerPage = 20,
pageFetchCallback = object : PageFetchCallback {
override fun onStart() {
Log.d("MitterUI", "Loading more messages")
}
override fun onEnd() {
Log.d("MitterUI", "Successfully loaded more messages")
}
}
)
ChatActivity.java
PaginationConfig paginationConfig = new PaginationConfig(
20,
3,
new PageFetchCallback() {
@Override
public void onStart() {
Log.d("MitterUI", "Loading more messages");
}
@Override
public void onEnd() {
Log.d("MitterUI", "Successfully loaded new messages");
}
}
);

Once you have your PaginationConfig configured to your needs, you can pass it to your ChannelWindowConfig instance that you defined inside your ChannelWindowManager instance in the previous step to override the default pagination behaviour.

Kotlin
Java
ChatActivity.kt
val channelWindowManager = ChannelWindowManager(
mitterUi,
"channel-id",
ChannelWindowConfig(
producers = listOf(
TextMessageProducer(mitterUi.mitter),
StringProducer()
),
paginationConfig = paginationConfig
)
)
ChatActivity.java
ChannelWindowManager channelWindowManager = new ChannelWindowManager(
mitterUi,
"channel-id",
new ChannelWindowConfig(
producers,
paginationConfig
)
);

Now, your manager will load up to 20 messages at a time instead of 10 and also you’ll start getting callbacks as the user starts scrolling up to fetch more messages.

This callback is useful when you want to show up a loader while fetching previous messages.

Wrap up

That’s all you need to and can do with the UI framework right now. It’s in a beta stage. Your valuable feedbacks will allow us to improve the UI framework experience in the future releases on this package.