Building a Chat App With ChatKit for Android

Did you know you can integrate Stream’s Android SDK with other open-source libraries? To show you just how easy it is, in this tutorial you’ll leverage data from Stream’s Chat API to power the messaging UI from ChatKit.

Specifically, this tutorial will cover:

  • Stream Setup
  • Implementing the ChatKit Interfaces
  • Building the Channels Screen
  • Building the Messaging Screen
  • Paging Data
  • Handling Input and Typing Events
  • Final Result

Understanding Stream’s Android UI Components Libraries

Before diving in, it may be helpful to get familiar with all of Stream’s Android component libraries.

Currently, Stream offers two sets of fully-featured, open-source UI components for Android messaging apps:

For more clarification on the Jetpack Compose and UI Components libraries, see Jetpack Compose vs. XML-based UI Components for Stream Chat.

However, Stream also offers lower-level libraries you can use on Android:

  • An Offline Support Library that provides caching, automatically retries failed calls, and offers observable data as Flows or LiveData objects.
  • A Low-level Chat Client that allows you to make API calls and subscribe to real-time chat events.

This tutorial will use ChatKit, an open-source chat UI library by Stfalcon, to build a basic messaging app on top of Stream's offline support library.

You'll be building on top of the offline library, observing the Chat state and displaying it using ChatKit.

Loading code sample...

Note: You can find the source code for this tutorial in the ChatKit Stream repository on GitHub.

Set Up Your Stream Account

For the purposes of this tutorial, it’s quicker and simpler to use the same hardcoded user defined in the Android Chat Messaging Tutorial.

This will allow you to avoid lots of extra steps, like creating user tokens, fetching your API key and Secret from your Stream dashboard, and creating sample data to populate your channels, among other things.

If you’d still like an account, you can sign up for a free Chat trial. Once you’re signed up, be sure to visit the Android Chat First Steps tutorial to learn how to use the Stream dashboard, create your first sample project, and more.

Implement the ChatKit Interfaces

ChatKit defines various interfaces that its Views can interact with, such as IDialog, IMessage, and IUser. To use ChatKit in your app, you need to create model objects that implement these interfaces.

The Stream Chat SDK already provides its own models for these types, so you’ll need to wrap these into the given interfaces. Let's take the example of just the Dialog (which is called a Channel in Stream's terminology):

Loading code sample...

This implementation of IDialog takes a Stream Channel object as its parameter and then reads the corresponding properties of the Channel as needed.

It also references implementations of the other two aforementioned ChatKit interfaces, which are similar simple wrappers:

Loading code sample...

Other than these models, ChatKit also needs an ImageLoader implementation so it can display user avatars.

Use the Coil library to provide this capability:

Loading code sample...

The interface is straightforward to implement with Coil:

Loading code sample...

With the models and image loader prepared, you’re ready to build a Channel List screen.

Build a Channels List Screen

Start with the XML layout, which will contain a full-screen DialogsList from ChatKit:

Loading code sample...

The DialogsList works with an adapter that will provide it the data that it should display (just like a RecyclerView). You can create this adapter as a property in your MainActivity, and then connect it to the view in an onCreate function:

Loading code sample...

Note: The adapter receives your CoilImageLoader implementation as a parameter.

Initializing Stream Chat

The next step is to initializethe Stream Chat SDK by instantiating the ChatClient and ChatDomain classes. To do so, you’ll need your app’s API Key and Secret.

For more information about these steps, see the Android In-App Messaging Tutorial. You’ll use the same hardcoded demo user as the tutorial.

Loading code sample...

With the connection ready, you can load your list of channels with the loadChannels method:

Loading code sample...

In the above code snippet, you:

  1. Created a filter to get channels that are of the messaging type where your current user is a member.
  2. Sorted these channels by last_updated so that channels with the newest messages show at the top. Thanks to the observable nature of the offline library, channels will update in real-time.
  3. Implemented QueryChannelsController from the offline library, which observes the current channel state. This state is automatically updated based on real-time WebSocket events as users send messages.
  4. Map new values to the Dialog instance and pass that list to the DialogsListAdapter whenever possible.

Now, you can build and run the application to see the list of loaded channels!

For comparison, here’s how the same screen looks when implemented with Stream’s UI Components:

Building the Messaging Screen

Next, you’ll create a new Empty Activity called MessagesActivity. For the XML layout, you’ll use a MessageList with a MessageInput below it:

Loading code sample...

This new screen will need to receive the channel's ID as a parameter, which you'll pass through Intent extras. It'll also use an adapter to display the list of messages, which you'll connect to MessagesList:

Loading code sample...

With the cid extracted from the Intent, you can now use ChatDomain again, this time to grab a ChannelController that’ll provide you real-time observable data for a single channel:

Loading code sample...

In the above snippet you:

  1. Clear messagesAdapter as you receive the entire list of messages when the observable data updates.
  2. Wrap all of your Stream message objects into the Message model (created earlier) that implements ChatKit’s IMessage interface.
    • You pass in true to reverse the order of messages here so that they display correctly in the list.

Paging Data

The code above sets you up for the initial load, where you load 30 messages for the current channel. However, as you scroll up, you need to load older messages.

You can load older messages by setting a listener on the messagesAdapter:

Loading code sample...

To make this simple, you’ll make your MessagesActivity implement the OnLoadMoreListener interface that’s expected here.

To load more messages, you’ll use the loadOlderMessages method of ChatDomain to load 30 more messages when the listener is triggered:

Loading code sample...

Handling Input and Typing Events

You have the code to load messages and to load even more when you scroll up. Now, you can set up the MessageInput so that you can also send messages of your own.

Add two listeners in the onCreate method:

Loading code sample...

And again, you’ll implement these with MessagesActivity:

Loading code sample...

The typing listener is very straightforward, as ChatDomain has the exact methods you need to call into.

When sending a new message, you perform basic input validation:

  • You check if there’s anything typed into the input field.
  • If there is, you create a Stream Message and send it using ChatDomain.

Final Result

Lastly, you need to set up navigation between screens.

To add screen navigation, add the following code in MainActivity:

Loading code sample...

If you build and run the app again, you’ll have a functional, basic chat app:

  • You have a list of available channels
  • You can open any of these channels
  • You can read messages in these channels
  • You can send your own messages as well!

For comparison, below is a messaging screen built using Stream’s UI Components (check out the library in the Chat UI Components sample app):

Wrapping Up

That’s it! You can find the completed project’s source code on GitHub.

To sum up, here’s what you’ve implemented in this sample app… :

  • Channel list
  • Real-time, visible channel updates (try sending a message from a second device to test it out)
  • Unread counts in the channel list
  • Avatar loading on both screens
  • Displaying and sending plain text messages
  • Message pagination (older content loads as you scroll up)

… and here are some possible improvements you could work on from here:

  • Channel list pagination
  • Placeholder images for channels with no set image URL
  • Rich messaging (images and other attachments)
  • Error handling for Stream API calls (this tutorial skips this step for simplicity)

If you’d like to try Stream’s own UI implementations, see the Jetpack Compose Chat Tutorial or the Android Chat Tutorial.

As always, happy coding!