Messengers remain one of the most popular applications on Android โ€”according to Statista, in 2026, more than 80% of smartphone users use at least one chat client. Creating your own analogue WhatsApp can become both a training project for beginning developers and the basis for a commercial startup. However, it is important to understand: copying the functionality of popular services requires compliance with laws on the protection of personal data (in the Russian Federation - 152-FZ, in the EU - GDPR)as well as publishing rules in Google Play.

In this guide we will analyze technical side developments: from choosing an architecture to push notification integration. You will learn how to organize real-time messaging, encrypt data, and optimize the application on weak devices. At the same time, we will not touch upon the issues of monetization or marketing - we will focus exclusively on Android-development and backend.

To successfully complete the project you will need:

  • ๐Ÿ“ฑ Android Studio (version Giraffe 2022.3.1 or newer)
  • ๐Ÿ”ฅ Firebase (for authentication and cloud functions)
  • ๐Ÿ’ป Knowledge Kotlin/Java and basics REST API
  • ๐Ÿ”Œ Stable Internet connection for testing notifications

1. Choosing a technology stack: what you need for a messenger

The first step is to decide on the tools. Classic WhatsApp uses Erlang for the server part and protocol XMPP (with modifications), but for an educational project this is redundant. We will offer a simplified stack that will allow you to deploy a working prototype in 2-3 weeks:

Component Recommended technologies Alternatives
Client (Android) Kotlin + Jetpack Compose Java + XML-markup
Server/backend Firebase Realtime Database + Cloud Functions Node.js + Socket.io
Authentication Firebase Authentication (by phone/email) OAuth 2.0 (via Google Sign-In)
Notifications Firebase Cloud Messaging (FCM) OneSignal or Pushy
Media storage Firebase Storage AWS S3 or Backblaze B2

โš ๏ธ Attention: If you plan to scale the project to thousands of users, Firebase it may be expensive - tariffs depend on the number of read/write operations. For testing, the free plan is enough (Spark Plan), but for production, consider self-hosting at VPS (for example, DigitalOcean or Hetzner).

Enough to get started - it provides ready-made solutions for authentication, database and notifications. However, if you need complete independence from third-party services, you will have to deploy your backend on Firebase - It provides ready-made solutions for authentication, database and notifications. However, if you need complete independence from third-party services, you will have to deploy your backend on Node.js/Python using WebSocket for real-time chat.

๐Ÿ“Š Which backend do you prefer for the messenger?
Firebase. (simplicity)
Own server (control)
Node.js + Socket.io (flexibility)
Not decided yet

2. Setting up Firebase: registering a project and connecting the SDK

Go to the website Firebase Console and create a new project. In the Project settings section, add Android application, specifying the package name (for example, com.yourcompany.messenger). After registration, download the file google-services.json โ€”it will be needed for integration with Android Studio.

In the root file build.gradle of your project, add the dependency:

dependencies {

classpath 'com.google.gms:google-services:4.4.1' // Check the current version

}

Then in the file build.gradle module (app) add:

apply plugin: 'com.google.gms.google-services'

dependencies {

implementation 'com.google.firebase:firebase-bom:32.7.2'

implementation 'com.google.firebase:firebase-auth-ktx'

implementation 'com.google.firebase:firebase-database-ktx'

implementation 'com.google.firebase:firebase-storage-ktx'

implementation 'com.google.firebase:firebase-messaging-ktx'

}

โš ๏ธ Attention: Library versions may change. Always check the current numbers in Firebase official documentation. After adding dependencies, sync the project with Gradle.

Now connect google-services.json:

  1. Copy the file to app/ your project folder.
  2. Make sure that settings.gradle is written:
dependencyResolutionManagement {

repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

repositories {

google()

mavenCentral()

}

}

โ˜‘๏ธ Preparing Firebase for work

Done: 0 / 5

3. Implementation of user authentication

Secure authentication is the basis of any messenger. We will use Firebase Authentication with entry by phone number (as in WhatsApp). To do this:

  1. B Firebase Console
  2. go to section Authentication โ†’ Sign-in method.
  3. Activate the method Phone.
  4. For testing, add your number to Phone numbers for testing (for example, +79123456789,123456, where 123456 is a test confirmation code).

Initialize in the code Firebase Auth and configure sending SMS:

// In your Activity/Fragment

private lateinit var auth: FirebaseAuth

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

auth = Firebase.auth

// Example of sending a code to a number

val phoneNumber = "+79123456789"

val options = PhoneAuthOptions.newBuilder(auth)

.setPhoneNumber(phoneNumber)

.setTimeout(60L, TimeUnit.SECONDS)

.setActivity(this)

.setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {

override fun onVerificationCompleted(credential: PhoneAuthCredential) {

// Automatic verification (for example, on an emulator)

signInWithPhoneAuthCredential(credential)

}

override fun onVerificationFailed(e: FirebaseException) {

// Error handling

}

override fun onCodeSent(

verificationId: String,

token: PhoneAuthProvider.ForceResendingToken

) {

// Save verificationId to confirm the code

}

})

.build()

PhoneAuthProvider.verifyPhoneNumber(options)

}

After receiving the SMS code, confirm it:

private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {

auth.signInWithCredential(credential)

.addOnCompleteListener(this) { task ->

if (task.isSuccessful) {

// The user is authorized

val user = task.result?.user

} else {

// Authentication error

}

}

}

โš ๏ธ Attention: Required in production implement:

  • ๐Ÿ”’ Protection against brute force codes (attempt limit)
  • ๐Ÿ“ฑ Checking the phone number format (regular expressions)
  • ๐Ÿ”„ Mechanism for resending SMS (with delay)
๐Ÿ’ก

To speed up development, use an Android emulator with a pre-configured phone number (in AVD Manager, select an image with Google Play Services).

4. Creating a chat interface: Jetpack Compose vs XML

The design of the messenger should be intuitive and responsive. We recommend using Jetpack Compose a modern framework for building a UI that simplifies working with dynamic lists (like in chats). An example of the basic structure of the message screen:

@Composable

fun ChatScreen(messages: List) {

LazyColumn(modifier = Modifier.fillMaxSize()) {

items(messages) { message ->

MessageBubble(message = message)

}

}

}

@Composable

fun MessageBubble(message: Message) {

val alignment = if (message.isFromCurrentUser) Alignment.End else Alignment.Start

Box(

modifier = Modifier

.fillMaxWidth()

.padding(8.dp),

contentAlignment = alignment

) {

Card(

shape = RoundedCornerShape(16.dp),

colors = CardDefaults.cardColors(

containerColor = if (message.isFromCurrentUser) Color.Blue else Color.Gray

)

) {

Text(

text = message.text,

modifier = Modifier.padding(12.dp),

color = Color.White

)

}

}

}

To work with Firebase Realtime Database use ViewModel:

class ChatViewModel : ViewModel() {

private val database = Firebase.database

private val messagesRef = database.getReference("messages")

val messages: LiveData> = liveData {

messagesRef.addValueEventListener(object : ValueEventListener {

override fun onDataChange(snapshot: DataSnapshot) {

val messageList = snapshot.children.map { it.getValue(Message::class.java) }

emit(messageList.filterNotNull())

}

override fun onCancelled(error: DatabaseError) {

// Error handling

}

})

}

fun sendMessage(text: String) {

val message = Message(

text = text,

timestamp = System.currentTimeMillis(),

senderId = Firebase.auth.currentUser?.uid ?: ""

)

messagesRef.push().setValue(message)

}

}

โš ๏ธ Attention: When using Realtime Database data is transmitted in clear text. To encrypt messages you will need:

  • ๐Ÿ” Implement end-to-end encryption (for example, using a library LibSignal)
  • ๐Ÿ”‘ Store encryption keys only on user devices
  • ๐Ÿ“œ Document the privacy policy (required for publication in Google Play)
Why shouldn't you use XML for chat?

XML markup becomes unwieldy when dynamically updating a list of messages. Jetpack Compose is better optimized for working with big data and animations, and also automatically handles element reuse (recycling), which is critical for chat performance.

5. Sending media files: photos, videos and documents

To exchange media in WhatsApp-clone you will need:

  1. Request permission (READ_EXTERNAL_STORAGE, CAMERA).
  2. Implement file selection via Intent.
  3. Upload files to Firebase Storage.
  4. Save a link to the file in Realtime Database.

Example code for loading an image:

private fun uploadImage(uri: Uri) {

val storageRef = Firebase.storage.reference

val imagesRef = storageRef.child("chat_images/${System.currentTimeMillis()}.jpg")

val uploadTask = imagesRef.putFile(uri)

uploadTask.addOnSuccessListener {

imagesRef.downloadUrl.addOnSuccessListener { downloadUri ->

// Save the link in the database

val message = Message(

imageUrl = downloadUri.toString(),

senderId = Firebase.auth.currentUser?.uid ?: "",

timestamp = System.currentTimeMillis()

)

Firebase.database.getReference("messages").push().setValue(message)

}

}.addOnFailureListener {

// Error handling

}

}

To take photos directly in the application, use CameraX:

private fun startCamera() {

val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

cameraProviderFuture.addListener({

val cameraProvider = cameraProviderFuture.get()

val preview = Preview.Builder().build()

val imageCapture = ImageCapture.Builder().build()

cameraProvider.unbindAll()

cameraProvider.bindToLifecycle(

this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture

)

}, ContextCompat.getMainExecutor(this))

}

โš ๏ธ Attention: When working with media files, consider:

  • ๐Ÿ“ธ Resolution CAMERA requires explanation to the user (starting from Android 6.0).
  • ๐Ÿ—‘๏ธ Clear the cache of downloaded files so as not to overload the device memory.
  • ๐Ÿ”„ For video, use compression (library FFmpeg or MediaCodec).
๐Ÿ’ก

To optimize traffic, media files should be compressed before loading. For example, for images you can use the Glide or Coil library with the quality(70) parameter.

6. Push notifications: FCM setting

Notifications of new messages are implemented via Firebase Cloud Messaging (FCM). this:

  1. Add to AndroidManifest.xml a service for processing notifications:
<service

android:name=".MyFirebaseMessagingService"

android:exported="false">

<intent-filter>

<action android:name="com.google.firebase.MESSAGING_EVENT" />

</intent-filter>

</service>

  1. Create a descendant class FirebaseMessagingService:
class MyFirebaseMessagingService : FirebaseMessagingService() {

override fun onMessageReceived(remoteMessage: RemoteMessage) {

remoteMessage.notification?.let {

showNotification(it.title, it.body)

}

}

private fun showNotification(title: String?, body: String?) {

val intent = Intent(this, ChatActivity::class.java).apply {

flags = Intent.FLAG_ACTIVITY_CLEAR_TOP

}

val pendingIntent = PendingIntent.getActivity(

this, 0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE

)

val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)

.setSmallIcon(R.drawable.ic_notification)

.setContentTitle(title)

.setContentText(body)

.setAutoCancel(true)

.setContentIntent(pendingIntent)

val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

val channel = NotificationChannel(

CHANNEL_ID,

"Message Notifications",

NotificationManager.IMPORTANCE_HIGH

)

notificationManager.createNotificationChannel(channel)

}

notificationManager.notify(0, notificationBuilder.build())

}

companion object {

const val CHANNEL_ID = "messenger_channel"

}

}

To send notifications from the server, use Cloud Functions:

const functions = require('firebase-functions');

const admin = require('firebase-admin');

admin.initializeApp();

exports.sendNotification = functions.database.ref('/messages/{messageId}')

.onCreate((snapshot, context) => {

const message = snapshot.val();

if (message.senderId !== message.receiverId) { // Don't send notification to yourself

const payload = {

notification: {

title: "New message",

body: message.text || โ€œYou have been sent a media file,โ€

icon: "default"

},

token: userFcmToken // Recipient's Token (must be stored in the database)

};

return admin.messaging().send(payload);

}

return null;

});

โš ๏ธ Attention: Starting from Android 13notifications require explicit user permission. Add to the manifest:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

And request it when you first start:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE)

}

7. Testing and performance optimization

Before publishing the application, test on:

  • ๐Ÿ“ฑ Different versions of Android (from 8.0 Oreo to 15).
  • ๐ŸŒ Different Internet speeds (3G, 4G, Wi-Fi).
  • ๐Ÿ”‹ Different battery levels (turn on saving mode).
  • ๐Ÿ“Š Large volumes of data (10,000+ messages).

To analyze performance, use Android Profiler v Android Studio:

  1. Open View โ†’ Tool Windows โ†’ Profiler.
  2. Run the application and simulate user activity.
  3. Check:
  • ๐Ÿ“ˆ CPU Usage โ€”there should not be a constant load >50%.
  • ๐Ÿ—œ๏ธ Memory โ€”no leaks.
  • ๐Ÿ”‹ Energy โ€”low consumption in the background.

Optimize work with Firebase:

  • ๐Ÿ”ฅ Use .indexOn in database rules to speed up queries.
  • ๐Ÿ“ฆ Limit the number of downloaded messages (.limitToLast(50)).
  • ๐Ÿ”„ Cache data locally with Room Database.

โš ๏ธ Attention: If your app consumes >1% battery per hour in the background Google Play it may be blocked for violating the energy efficiency policy Monitor metrics with Play Console.

๐Ÿ’ก

To test on real devices, use the Firebase Test Lab service - it allows you to run the application on various smartphone models in the cloud.

8. Publishing on Google Play: requirements and pitfalls

Before publishing, check compliance Google Play policies:

  • ๐Ÿ”ž Age rating (for messengers usually 3+ or 12+).
  • ๐Ÿ“„ Privacy Policy (required when collecting data).
  • ๐Ÿ”’ Data security (fill out the form in Play Console).
  • ๐Ÿ“ฑ Support Android 12+ (mandatory from 2023).

Prepare the following materials:

Element Requirements Example
Icon Size 512ร—512, without transparency Example of a messenger icon
Screenshots Minimum 2, resolution 1080ร—1920 Chat screen + registration screen
Description Up to 4000 characters, with keywords "Fast and secure messenger with encryption..."
Video Not necessary, but increases conversion Chat demo (15-30 sec)

โš ๏ธ Attention: If your messenger allows you to send files, indicate restrictions in the description:

  • ๐Ÿ“ Maximum file size (for example, 100 MB).
  • ๐ŸŽต Supported formats (for example, JPEG, PNG, MP4, PDF).
  • โฑ๏ธ Media storage time on the server (if applicable).

After downloading APK/AAB to Play Console expect moderation from 1 to 3 days. Common reasons for rejection:

  • ๐Ÿšซ Lack of privacy policy.
  • ๐Ÿ”ง Inconsistency with the declared functionality.
  • ๐Ÿ“ฑ Application crashes on test devices.
๐Ÿ“Š What functionality will you add to your messenger first?
Voice messages
Video calls
Stickers and GIF
Encrypted chats
Group chats

FAQ: Frequently asked questions when developing a messenger

๐Ÿ”น Is it possible to do without a server and use only P2P?

Technically yes - using WebRTC or Bluetooth/Wi-Fi Direct. However, this approach has limitations:

  • ๐Ÿ“ถ Users must be online at the same time.
  • ๐Ÿ”Œ It is impossible to send messages offline.
  • ๐Ÿ“ฑ It is difficult to implement group chats.

For a full-fledged messenger, a server necessary.

๐Ÿ”น How to implement end-to-end encryption (E2EE) like WhatsApp?

Use the protocol Signal Protocol (open standard). The library LibSignal provides ready-made solutions for Kotlin/Java. Basic steps:

  1. Generate key pairs (IdentityKey, SignedPreKey, OneTimePreKey) during registration.
  2. Exchange keys through the server (for example, in encrypted form).
  3. Encrypt messages using SessionCipher.

โš ๏ธ Store private keys only on the user's device!

๐Ÿ”น How much will hosting cost for 1000 users?

Approximate calculations for Firebase (tariff Blaze, pay-as-you-go):

  • ๐Ÿ”ฅ Authentication: ~$0.01 per 1000 phone number checks.
  • ๐Ÿ—„๏ธ Realtime Database: ~$1 per 100,000 read/write operations.
  • ๐Ÿ“ค Storage: ~$0.026 per GB of stored data.
  • ๐Ÿ”” FCM: Free up to 240 messages/min and 5000 notifications/day.

Total: ~$5โ€“$15/month with moderate activity. For comparison, renting VPS on DigitalOcean will cost ~$10/month, but will require independent assistance. server settings.

๐Ÿ”น Do you need to register the application with Roskomnadzor?

If your messenger:

  • ๐Ÿ“ฑ Distributed in Russia.
  • ๐Ÿ—ƒ๏ธ Stores personal data (phone numbers, names).
  • ๐Ÿ‘ฅ Has > 100,000 users per day.

Then you are subject to 152-FZ "On Personal Data" and should:

  1. Register with Roskomnadzor as a PD operator.
  2. Post the privacy policy in Russian.
  3. Ensure that data of users from the Russian Federation is stored on servers in Russia.

For small projects (up to 100k users), registration is not required, but a privacy policy is required.

๐Ÿ”น How to add support for multiple languages?

Use the standard localization mechanism in Android:

  1. Create folders values-es, values-fr etc. in res/.
  2. Place translated strings there (for example, strings.xml).
  3. Load translations via getString(R.string.message).

To dynamically change the language without restarting the application, use:

val config = Configuration(resources.configuration)

config.setLocale(Locale("es")) // Spanish

resources.updateConfiguration(config, resources.displayMetrics)

โš ๏ธ Donโ€™t forget to translate not only the UI, but also notifications, errors and system messages!