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:

Empty chat 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:

Example of the cloned homescreen

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:

https://gist.github.com/nparsons08/589ecaf1ae8a2dc30238000361dcf9cd

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:

https://gist.github.com/nparsons08/135e36d0c9aed4438a5ae3720c20b82b

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:

https://gist.github.com/nparsons08/7dafc26b3bd2f621dc372ee6bd7b13cc

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:

https://gist.github.com/nparsons08/2456957262e51beba42c2ef4f15f6188

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:

https://gist.github.com/nparsons08/4f6e38d38a146d3375b8e324865c0951

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:

https://gist.github.com/nparsons08/cd4e9b38ff3562510135d04b01c44e35

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:

https://gist.github.com/nparsons08/196d9dc001105e172910f8cff8809010

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:

https://gist.github.com/nparsons08/12ecd308ddc1ec2d145ece14899c93af

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

package com.example.whatsappclone.ui.channel

https://gist.github.com/nparsons08/b47e86116fafe8eeb2806bb975d5abdd

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

https://gist.github.com/nparsons08/efd697d05c83756c8a1c75e58d82c232

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:

https://gist.github.com/nparsons08/c674f4f66fb9aba7bfb69c611e0bd1f5

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:

https://gist.github.com/nparsons08/17934cd2f64bf3330d6724343753a2d8

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:

https://gist.github.com/nparsons08/d54c62c1ffe060fcd8b86fb4be1b94ec

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

https://gist.github.com/nparsons08/df9da66e2dc467587849f716d9f36980

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:

https://gist.github.com/nparsons08/b1a4cc536f3b660dd900237a48da09a2

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:

https://gist.github.com/nparsons08/a67a9d3c4e8c37912ac86b8ba7951b9b

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:

https://gist.github.com/nparsons08/6f2868e8032e754e2183e545021abe35

  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!

TutorialsChat