In this tutorial, we’ll be looking into how the introduction of Android Oreo has brought a drastic change in the Notifications. You aren’t far away from getting an Android Oreo update. Before you do get, let’s look into the working of notifications and what you need to change and adapt as Android Developer.
Android Notification Channel
We have discussed and implemented Notification, here and here.
With the introduction of Android Oreo, Google has strived the Notifications system more user-friendly. Android Oreo has completely redesigned notifications. The power to receive the kinds of notifications has been given in the hands of the end users. The reason this all became possible is: Notification Channels.
Notification Channels allow us to separate notifications into different groups/categories. Every channel would have a common functionality. It allows the user to customize their notification settings.
Thanks to this feature the user can do the following things from the Apps Settings:
- Block notifications from a particular channel.
- Set priority/silent on different notification channels.
Without configuring Notification Channels, you cannot build notification for applications with Android API >=26. Notification Channels would be ignored for older applications with the Android API Creating Notification Channel
Following code creates a notification channel:
1 2 3 4 5 6 7 8 |
NotificationChannel notificationChannel = new NotificationChannel(channel_id , channel_name, NotificationManager.IMPORTANCE_HIGH); notificationChannel.enableLights(true); notificationChannel.enableVibration(true); notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400}); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(notificationChannel); |
The NotificationChannel constructor requires us to specify the channel_id and channel_name strings. The Importance argument is an int which specifies the level of interruption by the notification. It can be one of the following values:
- IMPORTANCE_DEFAULT – Shows up in the system tray. Makes sound. Doesn’t visually pop up.
- IMPORTANCE_HIGH – Visually pops up too.
- IMPORTANCE_LOW – Shows in the tray. No pop up. No sound.
- IMPORTANCE_NONE – Doesn’t show up. Kind of blocked notifications.
Besides the public methods specified above, following are some handy methods that come with NotificationChannels.
-
setGroup()
/getGroup()
– Setters and getters for the channel. We’ll look at this later.
?
setBypassDnd()
?- Set the INTERRUPTION_PRIORITY_VALUE to bypass do not disturb.canBypassDnd()
– Check whether the notification channel can display notification in DND mode.setLockScreenVisibility()
– Set whether notifications from this channel should be displayed on the lock screen or not.canShowBadge()
– Can show the badge/notification dot on the application icon.getName()
/getId()
– Retrieve the channel name and id respectively.
Once the Notification Channel is created using createNotificationChannel()
, every notification created from it will have common properties unless modified.
Note: The above code snippet is valid for Android version Oreo and above only. Hence it must be enclosed in the following condition.
1 2 3 4 |
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { } |
Creating a Notification
The following code snippet creates a Notification from NotificationChannel.
1 2 3 4 5 6 7 |
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, "channel_id") .setContentTitle("Test Title") .setContentText("Test Message") .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(1, notification.build()); |
From Android Oreo, it is mandatory to specify the NotificationChannel id
in the Builder constructor itself.
Reading And Deleting Notification Channels
To retrieve a notification channel we can call the method getNotificationChannel() on the NotificationManager.
We need to pass the channel_id of the relevant channel.
Also, to retrieve a list of all NotificationChannels we can invoke the method getNotificationChannels().
1 2 3 |
List<NotificationChannel> notificationChannels = notificationManager.getNotificationChannels(); |
Deleting a NotificationChannel
To delete a NotificationChannel, the following snippet is used.
1 2 3 |
notificationManager.deleteNotificationChannel("channel_id"); |
Notification Channel Groups
A NotificationChannelGroup is used to created different categories for NotificationChannels.
The same NotificationChannels can be used in different circumstances depending on the group from which they are invoked.
You can have two groups named Hourly, Daily. All the Notification Channels would be present in both the groups. It’s your choice from which group you want to use the channel to create Notifications.
Creating NotificationChannelGroup
Following is one way to create NotificationChannelGroups.
1 2 3 4 5 6 |
List<NotificationChannelGroup> list = new ArrayList<>(); list.add(new NotificationChannelGroup(group_id_1, group_name_2)); list.add(new NotificationChannelGroup(group_id_2, group_name_2)); notificationManager.createNotificationChannelGroups(list); |
You need to set the group_id and group_name.
Furthermore, you need to set the group on the NotificationChannel too using setGroup()
. Pass in the group_id in the setGroup() method.
Modifying Notification from Settings
The end user can modify the Notification Channel from the Settings.
Settings | Apps | App Name | App Notifications
Alternatively we can redirect them from the app itself using Intents.
1 2 3 4 5 6 |
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_CHANNEL_ID, notificationChannel.getId()); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); startActivity(intent); |
We pass the channel id and app package name. This specifically opens the particular channel ID.
Let’s create a basic application with the use cases of NotificationChannel and NotificationChannelGroup
Project Structure
In this application, we’ll be using RadioButton to change between the groups and Spinners to choose the current NotificationChannel. The EditText would be used to set the body of the notification.
Android NotificationChannel Code
The code for the activity_main.xml
layout file 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 |
<?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" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Choose the group for the channel" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" android:padding="16dp" app:layout_constraintBottom_toTopOf="@+id/radioGroup"/> <RadioGroup android:id="@+id/radioGroup" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:orientation="horizontal" android:weightSum="2" app:layout_constraintBottom_toTopOf="@+id/spinner" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <RadioButton android:id="@+id/radioButton1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="First Type" android:layout_weight="1" android:textSize="18sp" /> <RadioButton android:id="@+id/radioButton2" android:layout_width="wrap_content" android:layout_weight="1" android:layout_height="wrap_content" android:text="Second Type" android:textSize="18sp" /> </RadioGroup> <Spinner android:id="@+id/spinner" style="@style/Widget.AppCompat.Spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" app:layout_constraintBottom_toTopOf="@+id/inContent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <EditText android:id="@+id/inContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:hint="Enter message" app:layout_constraintBottom_toTopOf="@+id/btnNotification" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <Button android:id="@+id/btnNotification" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Create New Notification" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
package com.journaldev.notificationchannels; import android.app.AlertDialog; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Build; import android.provider.Settings; import android.support.v4.app.NotificationCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener { List<String> data = new ArrayList<>(); int notifier_counter = 0; NotificationManager notificationManager; RadioButton radioButtonFirst; RadioButton radioButtonSecond; RadioGroup radioGroup; EditText editText; Button btnNotification; Spinner spinner; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); data.add("Bitcoin"); data.add("Ethereum"); data.add("Litecoin"); data.add("Ripple"); createNotificationGroups(); createNotificationChannels(); ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, data); dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(dataAdapter); spinner.setOnItemSelectedListener(this); radioGroup.check(R.id.radioButton1); btnNotification.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (editText.getText().toString().length() > 0) { String channel_id = ""; String group_id = ""; PendingIntent contentIntent = PendingIntent.getActivity(MainActivity.this, 0, new Intent(MainActivity.this, MainActivity.class), 0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { RadioButton radioButton = findViewById(radioGroup.getCheckedRadioButtonId()); group_id = radioButton.getText().toString(); channel_id = notificationManager.getNotificationChannel(spinner.getSelectedItem().toString() + "_" + group_id).getId(); contentIntent = PendingIntent.getActivity(MainActivity.this, 0, new Intent(MainActivity.this, MainActivity.class).putExtra("importance", notificationManager.getNotificationChannel(channel_id).getImportance()).putExtra("channel_id", channel_id), PendingIntent.FLAG_UPDATE_CURRENT); } NotificationCompat.Builder notification = new NotificationCompat.Builder(MainActivity.this, channel_id) .setContentTitle(spinner.getSelectedItem().toString()) .setContentText(editText.getText().toString()) .setGroup(group_id) .setContentIntent(contentIntent) .setSmallIcon(R.mipmap.ic_launcher); notifier_counter++; notificationManager.notify(notifier_counter, notification.build()); } else { Toast.makeText(getApplicationContext(), "Please enter something in EditText", Toast.LENGTH_LONG).show(); } } }); } @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { } @Override public void onNothingSelected(AdapterView<?> parent) { } private void initViews() { spinner = findViewById(R.id.spinner); btnNotification = findViewById(R.id.btnNotification); editText = findViewById(R.id.inContent); radioGroup = findViewById(R.id.radioGroup); radioButtonFirst = findViewById(R.id.radioButton1); radioButtonSecond = findViewById(R.id.radioButton2); } private void createNotificationGroups() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { List<NotificationChannelGroup> list = new ArrayList<>(); list.add(new NotificationChannelGroup(radioButtonFirst.getText().toString(), radioButtonFirst.getText())); list.add(new NotificationChannelGroup(radioButtonSecond.getText().toString(), radioButtonSecond.getText())); notificationManager.createNotificationChannelGroups(list); } } private void createNotificationChannels() { for (String s : data) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationChannel notificationChannel = new NotificationChannel(s + "_" + radioButtonFirst.getText().toString(), s, NotificationManager.IMPORTANCE_HIGH); notificationChannel.enableLights(true); notificationChannel.enableVibration(true); notificationChannel.setGroup(radioButtonFirst.getText().toString()); notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400}); NotificationChannel notificationChannel2 = new NotificationChannel(s + "_" + radioButtonSecond.getText().toString(), s, NotificationManager.IMPORTANCE_NONE); notificationChannel2.enableLights(true); notificationChannel2.enableVibration(true); notificationChannel2.setGroup(radioButtonSecond.getText().toString()); notificationChannel2.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400}); if (notificationManager != null) { notificationManager.createNotificationChannel(notificationChannel); notificationManager.createNotificationChannel(notificationChannel2); } } } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { final Bundle bundle = intent.getExtras(); int importance = -1; if (bundle != null) { importance = bundle.getInt("importance"); } if (importance != -1) { AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setMessage("Goto settings to change the Notification channel") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { updateNotificationSettings(bundle.getString("channel_id")); } }).setNegativeButton("CANCEL", null) .show(); } } } private void updateNotificationSettings(String channel_id) { Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel_id); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); startActivity(intent); } } |
In the above code, we begin by creating the NotificationChannelGroup and NotificationChannel for the RadioButtons and Spinners respectively.
The channel_id
is appended with the group_id
in order to know which group does the channel belong to.
On Button click we create the notification based on which radio button and spinners were selected and retrieve the channel_id and group_id accordingly.
For this example, by default, we’ve blocked notifications from all the channels belonging to second group.
(Enable that channel from the settings to view those notifications).
On a notification click, we show up a dialog that allows the user to goto the settings to change the current NotificationChannel settings.
On Notification click launches the same activity. Hence in the Manifest file we need to set android:launchMode="singleTop"
in the activity tag.
Output
The output of the above application in action is given below.
Long Press on a Notification, it allows us to change the current channel settings with the slider.
The All categories option that’s visible lets us view all types of channels and groups.
Do take note that we’d toggled a few channels from the first and second group. It lead to blocked notifications in the first group and default notifications in the second.
Android Notification Dots
Notification Dots/Badges are displayed on the app icon if there are unread notifications.
We can use the method setShowBage(boolean)
on the notification to display/hide the dot for the particular channel.
Thanks to Notification Dots, long pressing the app icon can now display/cancel the pending notifications too as shown below.
Do note that the count for the pending notifications is displayed in the Notification Dot menu too.
This brings an end to this tutorial. You can download the final Android NotificationChannels Project from the link below.