Push notifications are an essential part of modern mobile applications, enabling developers to engage users with timely and relevant information. While newer Android projects often leverage technologies like Jetpack Compose, many existing projects are still built using XML layouts and traditional Android components. Integrating push notifications into these XML-based projects requires a slightly different approach. This comprehensive guide walks you through the process of integrating push notifications using Firebase Cloud Messaging (FCM) in Android projects using XML layouts.
Why Integrate Push Notifications?
Push notifications enhance user engagement, deliver important updates, and drive user retention. Here are a few reasons why you should integrate push notifications:
- Real-Time Updates: Notify users about new content, updates, and events instantly.
- Increased Engagement: Encourage users to interact with your app regularly.
- Personalized Experience: Deliver personalized messages based on user preferences and behavior.
- Improved Retention: Bring users back to your app with valuable and timely reminders.
Overview of Firebase Cloud Messaging (FCM)
Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that allows you to reliably deliver messages at no cost. FCM provides a simple and reliable way to send push notifications and data messages from your server to Android, iOS, and web applications.
Prerequisites
Before you begin, ensure that you have the following:
- Android Studio: Installed and configured on your development machine.
- Firebase Account: A Google account to access Firebase services.
- Android Project: An existing Android project using XML layouts.
Step-by-Step Integration Guide
Step 1: Set Up Firebase Project
First, set up a new project on Firebase and link it to your Android app.
- Go to the Firebase Console.
- Click on “Add project” and follow the instructions to create a new project.
- Once the project is created, click on the Android icon to add your Android app to the Firebase project.
- Enter your app’s package name, provide a nickname, and optionally, add your app’s SHA-1 certificate.
- Download the
google-services.jsonfile and add it to theapp/directory of your Android project.
Step 2: Add Firebase Dependencies
Next, add the necessary Firebase dependencies to your project.
- Open your project-level
build.gradlefile (usually namedbuild.gradleat the root of your project) and add the Google Services plugin to thedependenciesblock:
dependencies {
classpath 'com.google.gms:google-services:4.3.15' // Check for the latest version
}
- Open your app-level
build.gradlefile (app/build.gradle) and add the Firebase dependencies:
plugins {
id 'com.android.application'
id 'com.google.gms.google-services' // Add this line
}
dependencies {
implementation platform('com.google.firebase:firebase-bom:32.7.0') // Check for the latest version
implementation 'com.google.firebase:firebase-messaging-ktx'
}
Make sure to sync your project after adding these dependencies.
Step 3: Create a Firebase Messaging Service
Create a class that extends FirebaseMessagingService to handle incoming messages.
- Create a new class named
MyFirebaseMessagingService.kt(orMyFirebaseMessagingService.java):
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import YOUR_APPLICATION_PACKAGE_NAME.MainActivity
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d(TAG, "From: ${remoteMessage.from}")
// Check if message contains a data payload.
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
sendNotification(remoteMessage.data)
}
// Check if message contains a notification payload.
remoteMessage.notification?.let {
Log.d(TAG, "Message Notification Body: ${it.body}")
it.body?.let { body -> sendNotification(mapOf("body" to body, "title" to (it.title ?: "Notification"))) }
}
}
override fun onNewToken(token: String) {
Log.d(TAG, "Refreshed token: $token")
sendRegistrationToServer(token)
}
private fun sendRegistrationToServer(token: String?) {
// TODO: Implement this method to send token to your app server.
Log.d(TAG, "sendRegistrationTokenToServer($token)")
}
private fun sendNotification(messageBody: Map) {
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_IMMUTABLE)
val channelId = getString(R.string.default_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle(messageBody["title"] ?: "Notification")
.setContentText(messageBody["body"] ?: "This is a push notification")
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
}
companion object {
private const val TAG = "MyFirebaseMsgService"
}
}
- Replace
YOUR_APPLICATION_PACKAGE_NAMEwith your app’s package name andMainActivitywith the appropriate main activity of your app. - Add the following to your
res/drawabledirectory (create the directory if it does not exist):
- Create
ic_stat_ic_notification.xmlwith content such as
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>
- Register the service in your
AndroidManifest.xmlfile:
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Step 4: Handle Notification Payload
Within MyFirebaseMessagingService, handle the incoming notification data.
- In the
onMessageReceivedmethod, extract the notification’s title and body from theRemoteMessage:
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d(TAG, "From: ${remoteMessage.from}")
// Check if message contains a data payload.
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
sendNotification(remoteMessage.data)
}
// Check if message contains a notification payload.
remoteMessage.notification?.let {
Log.d(TAG, "Message Notification Body: ${it.body}")
it.body?.let { body -> sendNotification(mapOf("body" to body, "title" to (it.title ?: "Notification"))) }
}
}
private fun sendNotification(messageBody: Map) {
// Your notification code here
}
- Implement the
sendNotificationmethod to create and display the notification usingNotificationCompat.Builder:
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import YOUR_APPLICATION_PACKAGE_NAME.MainActivity
private fun sendNotification(messageBody: Map) {
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_IMMUTABLE)
val channelId = getString(R.string.default_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle(messageBody["title"] ?: "Notification")
.setContentText(messageBody["body"] ?: "This is a push notification")
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
}
- Make sure to replace
YOUR_APPLICATION_PACKAGE_NAME.MainActivitywith the appropriate launch activity for your application.
Step 5: Requesting the FCM Registration Token
To send push notifications to a specific device, you need to retrieve the FCM registration token for that device. This token is automatically generated by the Firebase SDK when your app is installed and opened.
Here’s how you can fetch the FCM registration token:
import com.google.firebase.messaging.FirebaseMessaging
fun fetchFcmToken() {
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed", task.exception)
return@addOnCompleteListener
}
// Get new FCM registration token
val token = task.result
// Log and store the token if needed
Log.d(TAG, "FCM Token: $token")
// You can send this token to your server
sendRegistrationToServer(token)
}
}
private fun sendRegistrationToServer(token: String?) {
// TODO: Implement this method to send token to your app server.
Log.d(TAG, "sendRegistrationTokenToServer($token)")
}
Call fetchFcmToken() in your MainActivity, such as in the onCreate method.
Step 6: Add Notification Icon and Color
Customize your notification’s appearance by setting the icon and color.
- Place a
notification_icon.pngfile in yourres/drawablefolder. This icon will be used in the notification. - Set a custom color for the notification by adding the following line to your
colors.xml:
<color name="notification_color">#3F51B5</color>
- In your
sendNotificationmethod, set the icon and color:
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.notification_icon)
.setColor(ContextCompat.getColor(this, R.color.notification_color))
.setContentTitle(messageBody["title"] ?: "Notification")
.setContentText(messageBody["body"] ?: "This is a push notification")
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
Step 7: Test the Push Notifications
Finally, test whether your push notifications are working correctly.
- Run your Android app on a physical device or emulator.
- Use the Firebase Console to send a test notification:
- Go to the Firebase Console.
- Select your project.
- Navigate to "Cloud Messaging" under the "Engage" section.
- Click on "Send your first message".
- Enter the notification title and body.
- Select your target app.
- Send the test message.
Best Practices for Push Notifications
Consider the following best practices to optimize your push notification strategy:
- Personalization: Tailor notifications based on user preferences and behavior.
- Timing: Send notifications at the right time, considering the user’s time zone and habits.
- Frequency: Avoid overwhelming users with too many notifications.
- Relevance: Ensure notifications are relevant and provide value to the user.
- Localization: Translate notifications to support different languages and regions.
- Opt-In: Request explicit permission before sending push notifications.
- Deep Linking: Link notifications to specific sections within the app.
- Analytics: Track the performance of your notifications using Firebase Analytics.
Handling Click Events
You can configure your notifications to open a specific activity or perform a particular action when the user clicks on the notification. This is known as handling click events or notification intents.
Here’s how you can handle click events in your MyFirebaseMessagingService class:
-
Modify the Intent in your `sendNotification` method to target the activity you want to open:
private fun sendNotification(messageBody: Map) { val intent = Intent(this, YourTargetActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE) ... - Remember to replace `YourTargetActivity::class.java` with the actual class of the activity you wish to open.
-
You can pass additional data to the target activity via the `Intent`:
intent.putExtra("key", "value"); -
Retrieve this data in `YourTargetActivity`:
val value = intent.getStringExtra("key")
Troubleshooting
If you encounter issues with push notifications, consider the following troubleshooting steps:
- Check Firebase Console: Ensure the Firebase project is properly configured.
- Verify Dependencies: Make sure you have added the correct Firebase dependencies.
- Inspect Logs: Examine the Android Studio logs for any error messages.
- Validate Package Name: Ensure your app’s package name matches the one in the Firebase console.
- Test on Multiple Devices: Check if the issue is device-specific.
Advanced Usage: Data-Only Messages and Background Handling
FCM supports two types of messages: Notification messages and Data messages. Notification messages contain a predefined set of user-visible keys, while Data messages contain custom key-value pairs.
- Data-Only Messages: Useful for sending data that you process in your application without displaying a visible notification. The onMessageReceived method is always called when the app is in the foreground. In the background, Android handles the display of the notification.
- Background Handling: For complex background processing, you may use WorkManager or similar solutions triggered from your FirebaseMessagingService.
Conclusion
Integrating push notifications in Android XML-based projects with Firebase Cloud Messaging (FCM) requires careful setup, configuration, and implementation. Following this guide ensures that you can effectively engage users, deliver real-time updates, and enhance the overall user experience. Push notifications play a vital role in user retention and communication, making them an essential feature for any modern mobile application.