An Android Studio Notifications Tutorial

Notifications provide a way for an app to convey a message to the user when the app is either not running or is currently in the background. For example, a messaging app might notify the user that a new message has arrived from a contact. Notifications can be categorized as being either local or remote. A local notification is triggered by the app itself on the device on which it is running. On the other hand, remote notifications are initiated by a remote server and delivered to the device for presentation to the user.

Notifications appear in the notification drawer that is pulled down from the screen’s status bar, and each notification can include actions such as a button to open the app that sent the notification. Android also supports Direct Reply notifications, a feature that allows the user to type in and submit a response to a notification from within the notification panel.

This chapter outlines the implementation of local notifications within an Android app. The next chapter (An Android Studio Direct Reply Notification Tutorial) will cover the implementation of direct reply notifications.

An Overview of Notifications

When a notification is initiated on an Android device, it appears as an icon in the status bar. Figure 77-1, for example, shows a status bar with several notification icons:

Figure 77-1

To view the notifications, the user makes a downward swiping motion starting at the status bar to pull down the notification drawer, as shown in Figure 77-2:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

Figure 77-2

In devices running Android 8 or newer, performing a long press on an app launcher icon will display any pending notifications associated with that app, as shown in Figure 77-3:

Figure 77-3

Android 8 and later also supports notification dots that appear on app launcher icons when a notification is waiting to be seen by the user.

A typical notification will display a message and, when tapped, launch the app responsible for issuing the notification. Notifications may also contain action buttons that perform a task specific to the corresponding app when tapped. Figure 77-4, for example, shows a notification containing two action buttons allowing the user to delete or save an incoming message:

Figure 77-4

It is also possible for the user to enter an in-line text reply into the notification and send it to the app, as is the case in Figure 77-5 below. This allows the user to respond to a notification without launching the corresponding app into the foreground:

Figure 77-5

The remainder of this chapter will work through creating and issuing a simple notification containing actions. The topic of direct reply support will be covered in the next chapter entitled An Android Studio Direct Reply Notification Tutorial.

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

Creating the NotifyDemo Project

Select the New Project option from the welcome screen and, within the resulting new project dialog, choose the Empty Views Activity template before clicking on the Next button.

Enter NotifyDemo into the Name field and specify com.ebookfrenzy.notifydemo as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 33: Android 13 (Tiramisu) and the Language menu to Kotlin.

Designing the User Interface

The main activity will contain a single button, the purpose of which is to create and issue an intent. Locate and load the activity_main.xml file into the Layout Editor tool and delete the default TextView widget.

With Autoconnect enabled, drag and drop a Button object from the panel onto the center of the layout canvas, as illustrated in Figure 77-6.

With the Button widget selected in the layout, use the Attributes panel to configure the onClick property to call a method named sendNotification.

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

Figure 77-6

Select the Button widget, change the text property in the Attributes tool window to “Notify” and extract the property value to a string resource.

Creating the Second Activity

In this example, the app will contain a second activity which will be launched by the user from within the notification. Add this new activity to the project by right-clicking on the com.ebookfrenzy.notifydemo package name located in app -> kotlin+java and selecting the New -> Activity -> Empty Views Activity menu option to display the New Android Activity dialog.

Enter ResultActivity into the Activity Name field and name the layout file activity_result. Since this activity will not be started when the application is launched (it will instead be launched via an intent from within the notification), it is important to make sure that the Launcher Activity option is disabled before clicking on the Finish button.

Open the layout for the second activity (app -> res -> layout -> activity_result.xml) and drag and drop a TextView widget so that it is positioned in the center of the layout. Edit the text of the TextView so that it reads “Result Activity” and extract the property value to a string resource.

Creating a Notification Channel

Before an app can send a notification, it must create a notification channel. A notification channel consists of an ID that uniquely identifies the channel within the app, a channel name, and a channel description (only the latter two will be seen by the user). Channels are created by configuring a NotificationChannel instance and then passing that object through to the createNotificationChannel() method of the NotificationManager class. For this example, the app will contain a single notification channel named “NotifyDemo News”. Edit the MainActivity.kt file and implement code to create the channel when the app starts:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

.
.
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Color
 
class MainActivity : AppCompatActivity() {
 
    private var notificationManager: NotificationManager? = null
 
    override fun onCreate(savedInstanceState: Bundle?) {
.
.
        notificationManager = 
                  getSystemService(
                   Context.NOTIFICATION_SERVICE) as NotificationManager
 
        createNotificationChannel(
                "com.ebookfrenzy.notifydemo.news",
                "NotifyDemo News",
                "Example News Channel")
    }
 
    private fun createNotificationChannel(id: String, name: String,
                                            description: String) {
 
        val importance = NotificationManager.IMPORTANCE_LOW
        val channel = NotificationChannel(id, name, importance)
 
        channel.description = description
        channel.enableLights(true)
        channel.lightColor = Color.RED
        channel.enableVibration(true)
        channel.vibrationPattern = 
            longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
        notificationManager?.createNotificationChannel(channel)
    }
}Code language: Kotlin (kotlin)

The code declares and initializes a NotificationManager instance and then creates the new channel with a low importance level (other options are high, max, min, and none) configured with the name and description properties. A range of optional settings are also added to the channel to customize how the user is alerted to the arrival of a notification. These settings apply to all notifications sent to this channel. Finally, the channel is created by passing the notification channel object through to the createNotificationChannel() method of the notification manager instance.

Requesting Notification Permission

Before testing the application, the appropriate permissions must be requested within the manifest file for the application. Specifically, the application will require permission to post notifications to the user. Within the Project tool window, locate and double-click on the AndroidManifest.xml file to load it into the editor and modify the XML to add the permission:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ebookfrenzy.audioapp" >
 
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
.
.Code language: HTML, XML (xml)

The above step will be adequate to ensure that the user enables notification permission when the app is installed on devices running versions of Android predating Android 6.0. Notification access is categorized in Android as a dangerous permission because it gives the app the potential to compromise the user’s privacy. For the example app to function on Android 6 or later devices, code must be added to request permission at app runtime.

Edit the MainActivity.kt file and begin by adding some additional import directives and a constant to act as request identification codes for the permission being requested:

.
.
import android.Manifest
import android.content.pm.PackageManager
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
.
.
class MainActivity : AppCompatActivity() {
.
.
    private val NOTIFICATION_REQUEST_CODE = 101
.
.Code language: Kotlin (kotlin)

Next, a method needs to be added to the class, the purpose of which is to take as arguments the permission to be requested and the corresponding request identification code. Remaining with the MainActivity.kt class file, implement this method as follows:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

private fun requestPermission(permissionType: String, requestCode: Int) {
    val permission = ContextCompat.checkSelfPermission(this,
            permissionType)
 
    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                arrayOf(permissionType), requestCode
        )
    }
}Code language: Kotlin (kotlin)

Using the steps outlined in the “Making Runtime Permission Requests in Android” chapter of this book, the above method verifies that the specified permission has not already been granted before making the request, passing through the identification code as an argument.

When the request has been handled, the onRequestPermissionsResult() method will be called on the activity, passing through the identification code and the request results. The next step, therefore, is to implement this method within the MainActivity.kt file as follows:

override fun onRequestPermissionsResult(requestCode: Int,
                permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
 
    when (requestCode) {
        NOTIFICATION_REQUEST_CODE -> {
            if (grantResults.isEmpty() || grantResults[0]
                != PackageManager.PERMISSION_GRANTED
            ) {
 
                Toast.makeText(
                    this,
                    "Notification permission required",
                    Toast.LENGTH_LONG
                ).show()
            }
        }
    }
}Code language: Kotlin (kotlin)

The above code checks the request identifier code to identify which permission request has returned before checking whether or not the corresponding permission was granted. If permission is denied, a message is displayed to the user indicating that the app will not function and the record button is disabled.

Before testing the app, all that remains is to call the newly added requestPermission() method when the app launches. Remaining in the MainActivity.kt file, modify the onCreate() method as follows:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    requestPermission(Manifest.permission.POST_NOTIFICATIONS,
        NOTIFICATION_REQUEST_CODE)
.
.Code language: Kotlin (kotlin)

With the code changes complete, compile and run the app on a device or emulator running Android 13 or later. When the dialog shown in Figure 77-7 appears, click on the Allow button to enable notifications:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

Figure 77-7

After launching the app, place it in the background and open the Settings app. Within the Settings app, select the Apps option, select the NotifyDemo project, and, on the subsequent screen, tap the Notifications entry. The notification screen should list the NotifyDemo News category as being active for the user:

Figure 77-8

Before proceeding, ensure that notification dots are enabled for the app.

Although not a requirement for this example, it is worth noting that a channel can be deleted from within the app via a call to the deleteNotificationChannel() method of the notification manager, passing through the ID of the channel to be deleted:

val channelID = "com.ebookfrenzy.notifydemo.news"
notificationManager?.deleteNotificationChannel(channelID)Code language: Kotlin (kotlin)

Creating and Issuing a Notification

Notifications are created using the Notification.Builder class and must contain an icon, title, and content. Open the MainActivity.kt file and implement the sendNotification() method as follows to build a basic notification:

.
.
import android.app.Notification
import android.view.View
.
.
fun sendNotification(view: View) {
 
    val channelID = "com.ebookfrenzy.notifydemo.news"
 
    val notification = Notification.Builder(this@MainActivity,
            channelID)
            .setContentTitle("Example Notification")
            .setContentText("This is an example notification.")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setChannelId(channelID)
            .build() 
}Code language: Kotlin (kotlin)

Once a notification has been built, it needs to be issued using the notify() method of the NotificationManager instance. The code to access the NotificationManager and issue the notification needs to be added to the sendNotification() method as follows:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

fun sendNotification(view: View) {
 
    val notificationID = 101
    val channelID = "com.ebookfrenzy.notifydemo.news"
 
    val notification = Notification.Builder(this@MainActivity,
            channelID)
            .setContentTitle("Example Notification")
            .setContentText("This is an example notification.")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setChannelId(channelID)
            .build()
 
    notificationManager?.notify(notificationID, notification)
}Code language: Kotlin (kotlin)

Note that when the notification is issued, it is assigned a notification ID. This can be any integer and may be used later when updating the notification.

Compile and run the app and tap the button on the main activity. When the notification icon appears in the status bar, touch and drag down from the status bar to view the full notification:

Figure 77-9

Click and hold on the notification to view additional information:

Figure 77-10

Next, place the app in the background, navigate to the home screen displaying the launcher icons for all of the apps, and note that a notification dot has appeared on the NotifyDemo launcher icon as indicated by the arrow in Figure 77-11:

Figure 77-11

If the dot is not present, check the notification options for NotifyDemo in the Settings app to confirm that notification dots are enabled, as outlined earlier in the chapter. If the dot still does not appear, touch and hold over a blank area of the device home screen, select the Home Settings option from the resulting menu, and enable the Notification dots option.

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

Performing a long press over the launcher icon will display a popup containing the notification:

Figure 77-12

If more than one notification is pending for an app, the long press menu popup will contain a count of notifications (highlighted in the above figure). This number may be configured from within the app by making a call to the setNumber() method when building the notification:

val notification = Notification.Builder(this@MainActivity,
        channelID)
        .setContentTitle("Example Notification")
        .setContentText("This is an  example notification.")
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .setChannelId(channelID)
        .setNumber(10)
        .build()Code language: Kotlin (kotlin)

As currently implemented, tapping on the notification has no effect regardless of where it is accessed. The next step is configuring the notification to launch an activity when tapped.

Launching an Activity from a Notification

A notification should allow the user to perform some action, such as launching the corresponding app or taking another action in response to the notification. A common requirement is to launch an activity belonging to the app when the user taps the notification.

This approach requires an activity to be launched and an Intent configured to launch that activity. Assuming an app that contains an activity named ResultActivity, the intent would be created as follows:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

val resultIntent = Intent(this, ResultActivity::class.java)Code language: Kotlin (kotlin)

This intent needs to then be wrapped in a PendingIntent instance. PendingIntent objects are designed to allow an intent to be passed to other applications, essentially granting those applications permission to perform the intent at some point in the future. In this case, the PendingIntent object is being used to provide the Notification system with a way to launch the ResultActivity activity when the user taps the notification panel:

val pendingIntent = PendingIntent.getActivity(
                this,
                0,
                resultIntent,
                PendingIntent.FLAG_IMMUTABLE)Code language: Kotlin (kotlin)

All that remains is to assign the PendingIntent object during the notification build process using the setContentIntent() method.

Bringing these changes together results in a modified sendNotification() method, which reads as follows:

.
.
import android.app.PendingIntent
import android.content.Intent
import android.graphics.drawable.Icon
.
.
class MainActivity : AppCompatActivity() {
 
    fun sendNotification(view: View) {
 
        val notificationID = 101
        val channelID = "com.ebookfrenzy.notifydemo.news"
        val resultIntent = Intent(this, ResultActivity::class.java)
 
        val pendingIntent = PendingIntent.getActivity(
                this,
                0,
                resultIntent,
                PendingIntent.FLAG_IMMUTABLE
        )
 
        val notification = Notification.Builder(this@MainActivity,
                channelID)
                .setContentTitle("Example Notification")
                .setContentText("This is an  example notification.")
                .setSmallIcon(android.R.drawable.ic_dialog_info)
                .setChannelId(channelID)
                .setContentIntent(pendingIntent)
                .build()
 
        notificationManager?.notify(notificationID, notification)
    }
.
.Code language: Kotlin (kotlin)

Compile and rerun the app, tap the button, and display the notification drawer. This time, however, tapping the notification will cause the ResultActivity to launch.

Adding Actions to a Notification

Another way to add interactivity to a notification is to create actions. These appear as buttons beneath the notification message and are programmed to trigger specific intents when tapped by the user. The following code, if added to the sendNotification() method, will add an action button labeled “Open” which launches the referenced pending intent when selected:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

val icon: Icon = Icon.createWithResource(this, android.R.drawable.ic_dialog_info)
 
val action: Notification.Action = 
        Notification.Action.Builder(icon, "Open", pendingIntent).build()
 
val notification = Notification.Builder(this@MainActivity,
        channelID)
        .setContentTitle("Example Notification")
        .setContentText("This is an  example notification.")
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .setChannelId(channelID)
        .setContentIntent(pendingIntent)
        .setActions(action)
        .build()
 
notificationManager?.notify(notificationID, notification)Code language: Kotlin (kotlin)

Add the above code to the method and run the app. Issue the notification and note the appearance of the Open action within the notification (depending on the Android version, it may be necessary to pull down on the notification panel to reveal the Open action):

Figure 77-13

Tapping the action will trigger the pending intent and launch the ResultActivity.

Bundled Notifications

If an app tends to issue notifications regularly, there is a danger that those notifications will rapidly clutter both the status bar and the notification drawer providing a less-than-optimal experience for the user. This can be particularly true of news or messaging apps that send a notification every time a breaking news story or a new message arrives from a contact. Consider, for example, the notifications in Figure 77-14:

Figure 77-14

Now imagine if ten or even twenty new messages had arrived. To avoid this problem, Android allows notifications to be bundled into groups.

To bundle notifications, each notification must be designated as belonging to the same group via the setGroup() method, and an additional notification must be issued and configured as the summary notification. The following code, for example, creates and issues the three notifications shown in Figure 77-14 above but bundles them into the same group. The code also issues a notification to act as the summary:

 

You are reading a sample chapter from an old edition of the Android Studio Essentials – Kotlin Edition book.

Purchase the fully updated Android Studio Ladybug Kotlin Edition of this publication in eBook or Print format.

The full book contains 100 chapters and over 878 pages of in-depth information.

Learn more.

Preview  Buy eBook  

 

val GROUP_KEY_NOTIFY = "group_key_notify"
 
var builderSummary: Notification.Builder = Notification.Builder(this, channelID)
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .setContentTitle("A Bundle Example")
        .setContentText("You have 3 new messages")
        .setGroup(GROUP_KEY_NOTIFY)
        .setGroupSummary(true)
 
var builder1: Notification.Builder = Notification.Builder(this, channelID)
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .setContentTitle("New Message")
        .setContentText("You have a new message from Kassidy")
        .setGroup(GROUP_KEY_NOTIFY)
 
var builder2: Notification.Builder = Notification.Builder(this, channelID)
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .setContentTitle("New Message")
        .setContentText("You have a new message from Caitlyn")
        .setGroup(GROUP_KEY_NOTIFY)
 
var builder3: Notification.Builder = Notification.Builder(this, channelID)
        .setSmallIcon(android.R.drawable.ic_dialog_info)
        .setContentTitle("New Message")
        .setContentText("You have a new message from Jason")
        .setGroup(GROUP_KEY_NOTIFY)
 
var notificationId0 = 100
var notificationId1 = 101
var notificationId2 = 102
var notificationId3 = 103
 
notificationManager?.notify(notificationId1, builder1.build())
notificationManager?.notify(notificationId2, builder2.build())
notificationManager?.notify(notificationId3, builder3.build())
notificationManager?.notify(notificationId0, builderSummary.build())Code language: Kotlin (kotlin)

When the code is executed, a single notification icon will appear in the status bar even though the app has issued four notifications. Within the notification drawer, a single summary notification is displayed listing the information in each of the bundled notifications:

Figure 77-15

Pulling further downward on the notification entry expands the panel to show the details of each of the bundled notifications:

Figure 77-16

Summary

Notifications provide a way for an app to deliver a message to the user when the app is not running or is currently in the background. Notifications appear in the status bar and notification drawer. Local notifications are triggered on the device by the running app, while remote notifications are initiated by a remote server and delivered to the device. Local notifications are created using the NotificationCompat.Builder class and issued using the NotificationManager service.

As demonstrated in this chapter, notifications can be configured to provide users with options (such as launching an activity or saving a message) by using actions, intents, and the PendingIntent class. Notification bundling provides a mechanism for grouping notifications to provide an improved experience for apps that issue more notifications.