Android Notifications Overview

Android Notifications Overview

Today, we are going to discuss in-depth about notifications in Android and help you in starting your journey towards understanding how to use them and why they are important.

Yes, I know that there are many different types and styles of notifications and that it’s not easy to wrap your head around all of them. But after reading this series, I’m pretty sure you will have a great overview of notifications and how to use them.

The main goal of this article is to give a clear picture of the complete world of Android Notifications and making them simple and easy to understand. This article is meant for beginners as well as experienced developers.

So, without wasting any further time, let’s get started.

Notification anatomy:

Before diving into the different types of notification, you should have a clear understanding of the prefixed structure of Android notifications and the parameters you can use to customize them.

Structure:

The structure and style of androids basic notifications is determined by system templates that can vary by versions and manufacturer. This means that you can only define the content of the notification and not the design itself.

Here are the most important parts of a notification:

  • Small Icon: required and set with setSmallIcon()
  • Large Icon: optional and set with setLargeIcon()
  • Title: optional and set with setContentTitle()
  • Text: optional and set with setContentText()
  • App name: provided by the system
  • Time stamp: provided by the system but can be overriden with setWhen()

Basic notifications will cover almost all use cases you could ever think of but there is also a way to create your own custom layouts for your notifications that we will explore later on.

Now that you know the basic structure and most important parts of a notification let’s take a look at the different kinds of notifications and how you can implement them in your apps.

Different kinds of Notifications:

Notification can be displayed in different areas and formats, such as an icon in the status bar or a more detailed entry in the notification drawer. Therefore we will take a look at the different kinds of Notifications and how and when we can implement them.

Status bar and notification drawer:

This is the basic notification type we are all familiar with where the notification is first displayed as an icon and can be viewed in more detail using the notification drawer.

Now let’s look at a basic notification where we define a small icon, a title and content text.

val CHANNEL_ID = "CHANNEL_ID"
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Some great title")
    .setContentText("Really great content for this notification")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)

Notice that we need to pass a CHANNEL_ID String to the constructor of the NotificationCompat to make sure our notification is compatible with Android 8.0 (Oreo) or higher.

Heads-up notification:

Heads-up notifications briefly appear on top of your screen in a floating window and are used for important and urgent information. They appear the moment the app issues the request and can only be displayed if the device is unlocked.

Codewise they don’t differ much from normal Notifications. We only need to set the Priority to HIGH or MAX instead of DEFAULT and ask for the vibrate permission.

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Some great title")
    .setContentText("Really great content for this notification")
    .setPriority(NotificationCompat.PRIORITY_HIGH)

if (Build.VERSION.SDK_INT >= 21) builder.setVibrate(LongArray(0))

In this example we create a heads-up notification by setting the priority to high.

Note: I disabled the vibrate feature below so we don’t need to ask for the vibrate permission.

Lock screen:

To show our notifications on the Lock screen of an Android device we can use the Visibility parameter. Here are the three possible values:

  • VISIBILITY_PUBLIC show the full content of the notification
  • VISIBILITY_PRIVATE shows basic information like the icon and the title
  • VISIBILITY_SECRET doesn’t show the notification on the lock screen

Here is an example of a notification which is visible on the lock-screen.

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Lock screen Notification")
    .setContentText("Really great content for this notification")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setVisibility(VISIBILITY_PUBLIC)

We can also provide an alternative Notification which hides certain details of the Notification.

val publicBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Alternative notification")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Lock screen Notification")
    .setContentText("Really great content for this notification")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setVisibility(VISIBILITY_PRIVATE)
    .setPublicVersion(publicBuilder.build())

In this example we show a notification that hides the content of our normal notification on the lockscreen.

App icon badge:

App icon badges indicate new notifications with a colored badged on the app’s icon and are available on Android Orea(8.0) and higher. They can be accessed by a long press on the icon of the app.

These type of notification is enabled by default and we don’t need to configure anything to use them. But that doesn’t mean that we can’t customize them. We can, for example change the notification counter and the notification long press.

Here is an example where we customize the notification badged so that it shows the small icon instead of the big one.

val builder = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
    .setContentTitle("Badged Notification")
    .setContentText("New badged notification")
    .setSmallIcon(R.drawable.notification_icon)
    .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)

We can also disable notification badgeds using the showBadges method on our Notificationchannel if they don’t make sense for our app.

val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
    description = descriptionText
    setShowBadge(false)
}

Notification Actions:

Now as you have a clear understanding of the different types of notifications you can use in your app let’s take a look at how to let your notification perform actions like opening an activity of our app or displaying a progress bar.

OnClick action:

First, let’s look at how we can do something when the user clicks the content of our notification. For that, we need to create an action in this example a pendingIntent to open our MainActivity.

val intent = Intent(this, MainActivity::class.java)
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)

After that, we just need to set the content Intent on our Notification to our PendingIntent.

val builder = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
.setContentIntent(pendingIntent)

We can also enable the notification to automatically delete itself when the user taps the notification using the setAutoCancel() function.

val builder = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
.setAutoCancel(true)

Notification Action Buttons:

We can also set up to three action buttons to our notification that allows the users to respond to events quickly.

To add an action button to our notification we pass a PendingIntent to the addAction() function.

val intent = Intent(this, MainActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}

val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)

val builder = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
    .addAction(R.drawable.notification_icon, "Open Activity",
        pendingIntent)
    .setAutoCancel(true)

The addAction() function takes three parameters the action icon, button title and pendingIntent for the action.

Expandable notification:

Another action we can perform is expanding our notification to display even more information like large images or blocks of text. We can do so by calling the setStyle() function on our Notification.Builder.

val bitmap = BitmapFactory.decodeResource(resources, R.drawable.notification_icon)

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Simple expandable notification")
    .setContentText("Simple notification that can be expended")
    .setLargeIcon(bitmap)
    .setStyle(NotificationCompat.BigPictureStyle()
        .bigPicture(bitmap)
        .bigLargeIcon(null))

In this example we set the Style to NotificationCompat.BigPictureStyle and set a big image that will be shown when you expend the notification.

We can also display big blocks of text by setting the style to NotificationCompat.BigTextStyle.

.setStyle(NotificationCompat.BigTextStyle()
                .bigText("Some big text block"))

For more options refere to the offical documentation.

Progress bar:

Another common action is to show progress bars in your notifications and update them. This can be done using the setProgress() function which lets you set the progress of your process.

Here is a simple example of a notification with a progress bar.

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Simple progress bar notification")
    .setContentText("Simple notification with a progress bar ")

// Progress values
val PROGRESS_MAX = 100
val PROGRESS_CURRENT = 0

NotificationManagerCompat.from(this).apply {
    // Sets the initial progress to 0
    builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false)
    notify(7 , builder.build())

    for(i in 0 until 100){
        builder.setProgress(PROGRESS_MAX, i, false)
        HandlerThread.sleep(100)
    }

    // Updates the notification when the progress is done
    builder.setContentText("Download complete")
        .setProgress(0, 0, false)
    notify(7, builder.build())
}

For that we need to set a maximal progress and update it until the progress value reaches the maximum.

In this example, I used a simple for loop which sleeps the Thread for 100 milliseconds every time.

Show notification:

Now that you have a great overview of the various types and actions of notifications let’s look at how you can display your notifications.

For that, we just need to call the notify function and pass the notification id and the build notification.

with(NotificationManagerCompat.from(this)) {
    notify(id, builder.build())
}

Notification group:

Now that you are familiar with the structure and actions of notification let’s look at how we can group notifications together. This is often used by apps that can send many notifications in a short period of time like a social media app like WhatsApp.

To implement this you need to create a notification group and add your notification to it.

val GROUP_KEY = "com.android.example.KEY"
val groupBuilderOne = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Simple group notification")
    .setContentText("Simple notification group")
    .setGroup(GROUP_KEY)

In this example we create a group id and add our notification to this group.

We also need to provide a notification summary for versions lower than 7.0 (API 24) which we can do using the setGroupSummary() function.

val summaryNotification = NotificationCompat.Builder(this@MainActivity, CHANNEL_ID)
    .setGroup(GROUP_KEY)
    .setGroupSummary(true)

After that, we just need to display our notifications and they will be grouped together.

Notification channels:

Another very important part of notifications is the notification channel which is required for all notifications on Android Oreo (8.0) and above. Each channel defines the visual and auditory behavior of all notifications featured in the channel.

After creation, the user has full control over the channel and can enable and disable the different notification channels of your app and further controll the notification settings. You should create an own notification channel for each distinct type of notification.

Create a notification channel:

Now let’s look at how to create a notification channel and how to register it to the system.

private val CHANNEL_ID = "CHANNEL_ID"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    // Channel name
    val name = "Channel"
    val descriptionText = "Simple channel example"
    val importance = NotificationManager.IMPORTANCE_DEFAULT
    val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
        description = descriptionText
        // Disable badged notifications
        //setShowBadge(false)
    }
    // Register the channel with the system
    val notificationManager: NotificationManager =
        getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel)
}

In this example we create a simple notification channel and register it to the system using the createNotificationChannel() function.

Deleting a notification channel:

Sometimes we also need to delete a notification channel if we don’t need it anymore. We can do so using the deleteNotificationChannel() function on our NotificationManager.

private val CHANNEL_ID = "CHANNEL_ID"
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

notificationManager.deleteNotificationChannel(CHANNEL_ID)

Custom layout:

As talked about above it is also possible to create your fully custom layouts for your notification. This can be done by creating two custom layouts and setting them using the setCustomContentView() and setCustomBigContentView() functions.

Layouts:

We need to create two layouts one for the normal notification and one for the expanded notification view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <TextView
            android:id="@+id/notification_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/TextAppearance.Compat.Notification.Title" />

    <TextView
            android:id="@+id/notification_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/TextAppearance.Compat.Notification.Info" />

</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="256dp"
              android:orientation="vertical">

    <TextView
            android:id="@+id/notification_expanded_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Expended notification"
            style="@style/TextAppearance.Compat.Notification.Title" />

    <TextView
            android:id="@+id/notification_expanded_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Expand notification informations"
            style="@style/TextAppearance.Compat.Notification.Info" />

    <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/ic_launcher_background"/>

</LinearLayout>

Here we use the TextAppearance_Compat_Notification style to prevent design bugs on different devices like black text on a black background.

Set the layouts:

After creating the layout we can start creating our custom notification and showing them to the user.

For that we first need to get the layouts and save them into variables.

val notificationLayout = RemoteViews(packageName, R.layout.custom_notification_layout)
val notificationLayoutExpanded = RemoteViews(packageName, R.layout.custom_notification_expended_layout)

After that, we can create a Notification builder which uses these two layouts.

val customNotification = NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.notification_icon)
    .setStyle(NotificationCompat.DecoratedCustomViewStyle())
    .setCustomContentView(notificationLayout)
    .setCustomBigContentView(notificationLayoutExpanded)

Note: For this to work you also need to set the style to DecoratedCustomViewStyle.

After that you can already show your notification like we look at above. But we can also change the text and content of our layouts by using setter functions.

notificationLayout.setTextViewText(R.id.notification_title, "Title")
notificationLayout.setTextViewText(R.id.notification_info, "Expand for more information")

Conclusion:

You made it all the way until the end! Hope that this article helped you understand the basics of Android notifications and how you can use them in your projects.

If you have found this useful, please consider recommending and sharing it with other fellow developers.

Read these next: