In this tutorial, we’ll be implementing Bundled Notifications in our Android Application. We’ve seen and implemented Direct Reply for Android Nougat. If you aren’t aware about Notifications and Pending Intents do go through the tutorial before proceeding.
Android Bundled Notifications
Android Bundled Notifications are handy when you have too many notifications stacked vertically. Bundled Notifications collapse them all into a single notification with a group summary set on the group notification. This saves us from scrolling through all the notifications. Also, if you receive many single notifications at once, instead of getting multiple noisy notification sounds, bundled notifications would give a single notification sound.
A Bundle Notification is of the format:
- 1 Summary Notification
- N enclosed Single Notifications
We can set a separate tone for every single notification. Every notification of a bundle would have the same group key.
When a new notification is added, it gets merged in the bundle with the matching group key.
We can add a separate action on each notification click as well as a separate action on bundle notification click.
The Bundle Notification can exist without any enclosed notifications.
Creating a Bundle Notification
A Bundle Notification is defined in the following way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel groupChannel = new NotificationChannel("bundle_channel_id", "bundle_channel_name", NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(groupChannel); } NotificationCompat.Builder summaryNotificationBuilder = new NotificationCompat.Builder(this, "bundle_channel_id") .setGroup(bundle_notification_id) .setGroupSummary(true) .setContentTitle("Bundled Notification. " + bundleNotificationId) .setContentText("Content Text for bundle notification") .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(bundleNotificationId, summaryNotificationBuilder.build()); |
A Bundle Notification must have the setGroupSummary()
set to true.
The group key set in the setGroup()
method must be set in all the notifications that are to be enclosed in the bundle.
Note: A bundle_channel_id needs to defined since Android Oreo. We’ve discussed Notification Channels at length in a separate tutorial.
We’ve set the importance of the bundle notification channel to LOW
to avoid simultaneous sounds from the Bundle and single notifications. We’ll create a separate channel for single notifications.
In the sample application, we’ll be implementing the Bundle Notifications feature. We’ll be using a Button to create a new Bundle and another button to add new Single Notifications in the current Bundle.
On a notification click, we’ll cancel the notification and display it’s ID in a Toast.
We’ll not be focusing on push notifications from the backend in this tutorial. Our goal is to understand the Bundle Notifications feature only.
Android Bundled Notifications Project Structure
A Single Activity application.
Android Bundled Notifications Code
The code for the activity_main.xml
layout class is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.journaldev.bundlednotificationsexample.MainActivity"> <Button android:id="@+id/btnBundleNotification" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="New Bundle Notification" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btnSingleNotification" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Add Notification to Bundle" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/btnBundleNotification" /> </android.support.constraint.ConstraintLayout> |
The code for the MainActivity.java
class is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
package com.journaldev.bundlednotificationsexample; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.support.v4.app.NotificationCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button btnBundleNotification, btnSingleNotification; NotificationManager notificationManager; int bundleNotificationId = 100; int singleNotificationId = 100; NotificationCompat.Builder summaryNotificationBuilder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); btnBundleNotification = findViewById(R.id.btnBundleNotification); btnSingleNotification = findViewById(R.id.btnSingleNotification); btnBundleNotification.setOnClickListener(this); btnSingleNotification.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnBundleNotification: if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel groupChannel = new NotificationChannel("bundle_channel_id", "bundle_channel_name", NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(groupChannel); } bundleNotificationId += 100; singleNotificationId = bundleNotificationId; String bundle_notification_id = "bundle_notification_" + bundleNotificationId; Intent resultIntent = new Intent(this, MainActivity.class); resultIntent.putExtra("notification", "Summary Notification Clicked"); resultIntent.putExtra("notification_id", bundleNotificationId); resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); summaryNotificationBuilder = new NotificationCompat.Builder(this, "bundle_channel_id") .setGroup(bundle_notification_id) .setGroupSummary(true) .setContentTitle("Bundled Notification. " + bundleNotificationId) .setContentText("Content Text for bundle notification") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(resultPendingIntent); notificationManager.notify(bundleNotificationId, summaryNotificationBuilder.build()); break; case R.id.btnSingleNotification: bundle_notification_id = "bundle_notification_" + bundleNotificationId; resultIntent = new Intent(this, MainActivity.class); resultIntent.putExtra("notification", "Summary Notification Clicked"); resultIntent.putExtra("notification_id", bundleNotificationId); resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); //We need to update the bundle notification every time a new notification comes up. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { if (notificationManager.getNotificationChannels().size() < 2) { NotificationChannel groupChannel = new NotificationChannel("bundle_channel_id", "bundle_channel_name", NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(groupChannel); NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } } summaryNotificationBuilder = new NotificationCompat.Builder(this, "bundle_channel_id") .setGroup(bundle_notification_id) .setGroupSummary(true) .setContentTitle("Bundled Notification " + bundleNotificationId) .setContentText("Content Text for group summary") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(resultPendingIntent); if (singleNotificationId == bundleNotificationId) singleNotificationId = bundleNotificationId + 1; else singleNotificationId++; resultIntent = new Intent(this, MainActivity.class); resultIntent.putExtra("notification", "Single notification clicked"); resultIntent.putExtra("notification_id", singleNotificationId); resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder notification = new NotificationCompat.Builder(this, "channel_id") .setGroup(bundle_notification_id) .setContentTitle("New Notification " + singleNotificationId) .setContentText("Content for the notification") .setSmallIcon(R.mipmap.ic_launcher) .setGroupSummary(false) .setContentIntent(resultPendingIntent); notificationManager.notify(singleNotificationId, notification.build()); notificationManager.notify(bundleNotificationId, summaryNotificationBuilder.build()); break; } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Bundle extras = intent.getExtras(); if (extras != null) { int notification_id = extras.getInt("notification_id"); Toast.makeText(getApplicationContext(), "Notification with ID " + notification_id + " is cancelled", Toast.LENGTH_LONG).show(); notificationManager.cancel(notification_id); } } } |
btnSingleNotification
adds a Single Notification in the current bundle notification. If the current bundle notification doesn’t exist it creates one first.btnBundleNotification
creates a new Bundle Notification and updates the group key by incrementing the id.- We set a
bundleNotificationId
equal to thesingleNotificationId
initially. - Every time a new bundle notification is created, we increment the
bundleNotificationId
by 100. - Every time a single notification is created inside the bundle notification, we increment the
singleNotificationId
by 1 on the current bundleNotificationId. - We’ve created separate channels for Bundle and Single Notifications.
onNewIntent
is triggered on Notification click. It gets the notification id from the intent data and cancels the respective notification.
Android Bundled Notifications App Output
Let’s see the output from the above code.
Do note that in the above output, clicking any notification cancels only the last notification added.
Why?
Because PendingIntent updates the data to the latest and we’re using request code as 0 for each of the PendingIntents. So it returns the latest data only.
We need to set a different request code for each notification.
Let’s set the request code as bundleNotificationId
for bundle notifications and singleNotificationId
for the single ones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
@Override public void onClick(View v) { switch (v.getId()) { case R.id.btnBundleNotification: if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel groupChannel = new NotificationChannel("bundle_channel_id", "bundle_channel_name", NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(groupChannel); } bundleNotificationId += 100; singleNotificationId = bundleNotificationId; String bundle_notification_id = "bundle_notification_" + bundleNotificationId; Intent resultIntent = new Intent(this, MainActivity.class); resultIntent.putExtra("notification", "Summary Notification Clicked"); resultIntent.putExtra("notification_id", bundleNotificationId); resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent resultPendingIntent = PendingIntent.getActivity(this, bundleNotificationId, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); summaryNotificationBuilder = new NotificationCompat.Builder(this, "bundle_channel_id") .setGroup(bundle_notification_id) .setGroupSummary(true) .setContentTitle("Bundled Notification. " + bundleNotificationId) .setContentText("Content Text for bundle notification") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(resultPendingIntent); notificationManager.notify(bundleNotificationId, summaryNotificationBuilder.build()); break; case R.id.btnSingleNotification: bundle_notification_id = "bundle_notification_" + bundleNotificationId; resultIntent = new Intent(this, MainActivity.class); resultIntent.putExtra("notification", "Summary Notification Clicked"); resultIntent.putExtra("notification_id", bundleNotificationId); resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); resultPendingIntent = PendingIntent.getActivity(this, bundleNotificationId, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); //We need to update the bundle notification every time a new notification comes up. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { if (notificationManager.getNotificationChannels().size() < 2) { NotificationChannel groupChannel = new NotificationChannel("bundle_channel_id", "bundle_channel_name", NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(groupChannel); NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } } summaryNotificationBuilder = new NotificationCompat.Builder(this, "bundle_channel_id") .setGroup(bundle_notification_id) .setGroupSummary(true) .setContentTitle("Bundled Notification " + bundleNotificationId) .setContentText("Content Text for group summary") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(resultPendingIntent); if (singleNotificationId == bundleNotificationId) singleNotificationId = bundleNotificationId + 1; else singleNotificationId++; resultIntent = new Intent(this, MainActivity.class); resultIntent.putExtra("notification", "Single notification clicked"); resultIntent.putExtra("notification_id", singleNotificationId); resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); resultPendingIntent = PendingIntent.getActivity(this, singleNotificationId, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); //singleNotificationId is set as the request code. NotificationCompat.Builder notification = new NotificationCompat.Builder(this, "channel_id") .setGroup(bundle_notification_id) .setContentTitle("New Notification " + singleNotificationId) .setContentText("Content for the notification") .setSmallIcon(R.mipmap.ic_launcher) .setGroupSummary(false) .setContentIntent(resultPendingIntent); notificationManager.notify(singleNotificationId, notification.build()); notificationManager.notify(bundleNotificationId, summaryNotificationBuilder.build()); break; } } |
Let’s see the output now.
Works!. This brings an end to android bundled notifications tutorial. You can download the Android BundledNotificationsExample Project from the link below.