Build a WhatsApp Clone on Android With Kotlin – Part 1

Android development has come a long way over the last few years. The Jetpack components significantly speed up development. In this tutorial, we’ll build a functional clone of WhatsApp with Kotlin. Building a messaging app used to be difficult; in this tutorial, you’ll get a chat experience up and running in roughly 20 minutes. The first part of this tutorial focuses on the chat functionality of WhatsApp, and in part 2, we’ll cover audio and video calling. Time to get started!

If you get lost during this tutorial you can have a look at:

The end result will look similar to this:

Clone the WhatsApp Starter Repo

Start by cloning the starter branch of the WhatsApp Clone Github repo:

git clone -b starter git@github.com:GetStream/WhatsApp-Clone-Android.git

The starter branch sets up a few simple things so you can skip the boilerplate:

  • Dependencies in the build.gradle files
  • Colors
  • Menu items
  • Drawables

Open the WhatsApp-Clone-Android directory in Android Studio and wait for the Gradle sync to complete. For this tutorial, you’ll want to use the Pixel 3 / API Level 29 Emulator for testing. After you’ve got that setup run the application. You should now see this empty screen:

Setup the Home Screen

As a first step, we’re going to create the home screen of your app. Which will look like this:

As an experienced Android developer, you’ll see a few things in this screen:

In this tutorial, we’ll follow Google’s recommendation of having 1 activity and many fragments. We’ll connect the fragments using the navigation component. Let’s get started.

Open up activity_main.xml and replace the TextView tag with a NavHostFragment:

The app:navGraph property tells the navigation component to use your navigation/nav_graph.xml file for navigation. Let’s take a moment to review the contents of nav_graph.xml:

Note how @id/nav_home is specified as the startDestination in the navigation tag. This makes the navigation component automatically load the homeFragment. As a next step, we’re going to make the homeFragment look like WhatsApp.

Open up fragment_home.xml and replace the contents with this:

The TabLayout provided by the material UI package combined with the ViewPager2 gives you the helpful swipeable tabs that you see in the WhatsApp interface. The FloatingActionButton handles the UI for creating a new conversation.

As a next step open up com.example.whatsappclone.ui.home.HomeFragment.kt
You’ll see an empty fragment class with a TODO. Let’s start by updating the constructor to point to the R.layout.fragment_home layout. Update the HomeFragment class to look like this:

class HomeFragment : Fragment(R.layout.fragment_home) {
}

This short syntax for the fragment creation is enabled by the androidx.navigation:navigation-fragment-ktx dependency. It's a nice improvement compared to overwriting the onCreate method. If you run your app, you’ll see the following UI:

The tabs are working, but the viewPager and the toolbar aren’t setup yet. Go back to com.example.whatsappclone.ui.home.HomeFragment.kt and update it to match this:

This looks like much code, but it’s pretty simple once you take a moment to review. The viewPager2 requires an adapter. We’ve set up this adapter to return an emptyFragment for tabs 0, 2, and 3 and return a ChannelListFragment for tab number 1.

The loop over the tabs just below getColorStateList handles the tint for the camera icon. This makes sure that it looks gray when it’s not selected and white when active.

The TabLayoutMediator connects the viewpager 2 with the tab UI. Try rerunning the app, and you’ll see it now looks much closer to the WhatsApp interface. The toolbar is rendering, and you can swipe through the tabs.

Channel List

Next, we’re going to render a list of channels/conversations. First, open up the empty fragment_channel_list.xml layout and replace it with this content:

We are using the ChannelListView which is a custom view provided by Stream. This view makes it easy to render a list of channels.

Note: You can also build your channel list view using the underlying API client. (But that’s more work, so we’re not doing it in this tutorial).

Next op up ChannelListFragment.kt. You’ll see an empty fragment. Replace the file’s content with this:

A few things to note in the above ChannelListFragment. We’re instantiating the Stream client and connecting a user. After that we configure the channelList component using viewModel.setChannelFilter(filter). In the tutorial, we’re using the example API key provided by Stream. Note that this example account is wiped regularly. So if you’re building a production app, you’ll want to register to get your own Stream Chat API key.

We’re also using the modern Kotlin syntax for getting a viewModel which is pretty nifty:

val viewModel: ChannelListViewModel by viewModels()

Last, observe how we’re using the navigation component and Kotlin safe args to navigate from the channel list to the channel. The HomeFragmentDirections class is auto-generated from nav_graph.xml, and Android knows how to navigate between the various views using the defined actions in nav_graph.xml. The argument tags in nav_graph.xml ensure type safety:

Time to run your application. The resulting code should render an app app that looks like this:

Channel UI

You’ll notice that if you click on a channel, you see an empty page. In this next step, we’re going to implement the chat/channel interface. The channel UI will look like this:

This UI is a MessageListView and a custom message input. For the message input view, we’ll use two-way databindings.

As a first step, open up the empty fragment_channel.xml and replace the content with:

Next, open up the ChannelFragment.kt file and replace its contents with this:

package com.example.whatsappclone.ui.channel

A few things to note in the above code. We are instantiating a Stream channel object using the safe args passed by the Navigation Component:

var channel = client.channel(args.channelType, args.channelId)

And we are creating a viewChannel based on that

If you run the app you’ll now see a channel list with a very minimalistic message input view:

Custom Message Input View & Data Binding

In this section of the tutorial, we’ll build our message input view. This is a great use case for Google’s live data two-way data-binding feature. Note how the design changes slightly if the user has entered text. This first image shows the layout when no text is entered:

The next image displays the layout when the user entered the text. Note how the icon changed from "record" into "send". Additionally, the camera icon disappeared:

Open the empty view_message_input.xml layout and update it to match this content:

Most of this layout will be familiar to you now. There is a floating action button (FAB) for the voice record icon and a constraint layout with a background for the message input and buttons. The most exciting bit is this tag on the EditText view:

android:text="@={viewModel.messageInputText}"

The @= syntax creates a double way binding between viewModel.messageInputText and the edit text element. As soon as one of the items changes, the other changes as well. Now that we have the double way binding setup, we can use it to:

  • Change the voice icon into a send icon
  • Remove the camera icon

Note how we import two utility methods using the data’s import tag:

Next, we apply the view utility method on the take picture button:

android:visibility="@{TextUtils.isEmpty(viewModel.messageInputText) ? View.VISIBLE : View.GONE}"

That line causes the android visibility to be set to GONE whenever the user types a message. For the floating action button, we switch the icon like this:

android:src="@{TextUtils.isEmpty(viewModel.messageInputText) ? @drawable/ic_mic_black_24dp : @drawable/ic_send_black_24dp}"

Now that we have the layout ready let's create our own custom message input view. Open the file com.example.whatsappclone.ui.views.MessageInputView.kt and update it to match this content:

Next, open up fragment_channel.xml and replace the EditText node with this:

Last, open up ChannelFragment.kt and uncomment this line:

//binding.messageInputView.setViewModel(viewModel!!, this)

Now when you run your app, you’ll see a functional and much better-looking message input. Data binding is a compelling concept. You can learn more about it in this tutorial.

Channel List ViewHolder

The Channel List already looks quite good. Let’s see if we can give it a little upgrade though. We’re going to use a custom viewHolder to customize the design.

Open the file CustomChannelListItemViewHolder.kt and replace the empty viewholder with the following code:

The above code updated the date format to be closer to WhatsApp’s layout. To use this viewHolder, we need to create a viewHolderFactory and apply it to the ChannelList view.

Open the file CustomViewHolderFactory.kt and replace it with the following code:

As the last step, we need to tell the ChannelList view about the custom view holder. Open ChannelListFragment.kt and uncomment this line:

// adapter.setViewHolderFactory(CustomViewHolderFactory())

If you run the app, the date format of the channel list will now look like WhatsApp’s format. If you got stuck at any point during this tutorial, note that you can always find the full source code on GitHub.

Customizing the Stream Chat Android SDK

The Views and ViewModels provided by stream-chat-android make it extremely simple to add chat to your application. Depending on your use case, there are several options available to customize the UI.

  1. The most common tweaks are handled using properties. This allows you to, for instance, change the message colors. The tutorial used this approach in fragment_channel.xml to customize the colors and background:

  1. The views allow you to specify your layout, enabling you to make simple layout changes.

  2. For more considerable UI changes, you can use your ViewHolder. In this tutorial, we used that approach for customizing the ChannelListView.

  3. Alternatively, you can also use the low-level client to build your UI or your view models. In this tutorial, we did that for the MessageInputView.

With these 4 levels of customizations, you can build any chat or messaging experience. The Android Chat Docs cover the chat SDK in more detail.

Final Thoughts

I hope you enjoyed this tutorial. It’s incredible how quickly you can build an app like WhatsApp these days. Kotlin and Android Jetpack have been a significant step forward for the Android ecosystem. Looking at a successful app like WhatsApp and trying to build it is a great way to learn. For a real app, you’ll, of course, want to come up with something new and different. You can use a similar approach to build any chat or messaging experience. Here are a few links to help out if you got stuck along the way or want to learn more:

Stream also has tutorials available for iOS Chat, React Chat, and React Native Chat in case you need to support multiple platforms.

Note: Part 2 of this tutorial is expected to go live in February and will explain how to add video and voice calls to your app.

Happy Chatting!