Android Notifications Tutorial: Getting Started

In this Android Notifications tutorial, you will learn how to create an App that allows you to set reminders at different schedules. By Kyle Jablonski.

4.2 (22) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Taking it a step further with Alarms

Issuing a notification after an action is performed is cool, but you are building a reminder application to administer medicine to your pets based on a schedule. This requires you to issue the notifications at some point in the future. The AlarmManager system service allows you to do just that. Notice, each item in the list of reminders includes the days and time to administer the medicine. You will use this information to schedule the alarms in the next section.

Creating an Alarm to send a Notification

In order to create an alarm to trigger a notification, you will need to do the following:

  1. Create notification channels for each pet type.
  2. Determine the time and date to schedule the alarm.
  3. Create a PendingIntent to add to the alarm.
  4. Schedule it with the AlarmManager.
  5. Register a BroadcastReceiver to listen for the alarm.
  6. Create the notification and issue it.

Since you already have experience creating notification channels, just copy this code and paste it into the onCreate() method of the application class PetRx.kt.

// 1
NotificationHelper.createNotificationChannel(this,
    NotificationManagerCompat.IMPORTANCE_LOW, true,
    ReminderData.PetType.Cat.name, "Notification channel for cats.")
// 2
NotificationHelper.createNotificationChannel(this,
    NotificationManagerCompat.IMPORTANCE_HIGH, true,
    ReminderData.PetType.Dog.name, "Notification channel for dogs.")
// 3
NotificationHelper.createNotificationChannel(this,
    NotificationManagerCompat.IMPORTANCE_NONE, false,
    ReminderData.PetType.Other.name, "Notification channel for other pets.")

Notice that, for each channel, you specify a different level of importance. When creating channels, you should carefully think about the user experience you are creating.

  1. Cats: NotificationManagerCompat.IMPORTANCE_LOW – Low notification importance: shows everywhere, but is not intrusive.
  2. Dogs: NotificationManagerCompat.IMPORTANCE_HIGH – Higher notification importance: shows everywhere, allowed to makes noise and peek.
  3. Other: NotificationManagerCompat.IMPORTANCE_NONE – A notification with no importance: shows nowhere, is blocked.

Because the application supports multiple pet types, and you want to stay organized, it’s important to add a channel for each different pet type. This will allow your users to configure specific behavior based on that pet’s type and control them individually from the device’s app settings.

Open the overflow menu and tap Manage Channels to see the registered channels for your application.

The sample app contains a model named ReminderData. Each ReminderData object contains both a time and a list of days. When the user creates the reminder, this information is input and saved to a local database. You’ll make use of the ReminderData when working with the AlarmManager to create an alarm. More on this in a bit.

Open the AlarmScheduler.kt file under the notif package and navigate to the createPendingIntent(): PendingIntent? method.

Add the following code:

// 1
val intent = Intent(context.applicationContext, AlarmReceiver::class.java).apply {
  // 2
  action = context.getString(R.string.action_notify_administer_medication)
  // 3
  type = "$day-${reminderData.name}-${reminderData.medicine}-${reminderData.type.name}"
  // 4
  putExtra(ReminderDialog.KEY_ID, reminderData.id)
}
// 5
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

Here you:

  1. Create the Intent with a destination of AlarmReceiver::class.java (this class should already exist, but you’ll fill it in in the next section).
  2. Set the action for the Intent.
  3. Set the type – This has to be unique so you construct it using the day, the pet’s name, the medicine and the pet type. If this is not unique it will overwrite any other PendingIntent with this same type. If you are interested you can read more about how two Intent’s are considered equal.
  4. Add the reminder’s ID in the Intent‘s bundle so you can use it in the AlarmReceiver.
  5. Create the PendingIntent using the getBroadcast() method. This is very important because you are creating the Intent with a BroadcastReceiver as a target.

Now that the notification channels are set up and you have created a unique PendingIntent for each reminder, it’s time to actually schedule the alarms.

Open the AlarmScheduler.kt file and find the scheduleAlarm() method.
Add the following code:

// 1
val datetimeToAlarm = Calendar.getInstance(Locale.getDefault())
datetimeToAlarm.timeInMillis = System.currentTimeMillis()
datetimeToAlarm.set(HOUR_OF_DAY, reminderData.hour)
datetimeToAlarm.set(MINUTE, reminderData.minute)
datetimeToAlarm.set(SECOND, 0)
datetimeToAlarm.set(MILLISECOND, 0)
datetimeToAlarm.set(DAY_OF_WEEK, dayOfWeek)
// 2
val today = Calendar.getInstance(Locale.getDefault())
if (shouldNotifyToday(dayOfWeek, today, datetimeToAlarm)) {
  alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP,
      datetimeToAlarm.timeInMillis, (1000 * 60 * 60 * 24 * 7).toLong(), alarmIntent)
  return
}
// 3
datetimeToAlarm.roll(WEEK_OF_YEAR, 1)
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP,
    datetimeToAlarm.timeInMillis, (1000 * 60 * 60 * 24 * 7).toLong(), alarmIntent)

You just did three important things:

  1. Set up a Calendar for the alarm’s time using the ReminderData.
  2. Checked whether that alarm should be scheduled today and scheduled it if so.
  3. Else, schedule the alarm to repeat every week at that time.

5. Create a BroadcastReceiver

There are just a few more things to consider when dealing with alarms, but how do they tie back into the notifications? Well, the instances of PendingIntent you registered through the AlarmManager were created using the PendingIntent.getBroadcast() method. This method allows you to send a broadcast to the system that notifies any listeners of the action you set for the Intent.

Open a file named AlarmReceiver in the notif package. Make sure it inherits from the BroadcastReceiver() base class and implement the onReceive() method.

if (context != null && intent != null && intent.action != null) {
  // 1
  if (intent.action!!.equals(context.getString(R.string.action_notify_administer_medication), ignoreCase = true)) {
    if (intent.extras != null) {
     // 2
      val reminderData = DataUtils.getReminderById(intent.extras!!.getInt(ReminderDialog.KEY_ID))
      if (reminderData != null) {
        // 3
        NotificationHelper.createNotificationForPet(context, reminderData)
      }
    }
  }
}

With this code, you:

  1. Check that the Intent’s action matches the one from the Intent you created above.
  2. Looked up the ReminderData in the database using the extra from the Intent’s Bundle.
  3. Create the notification using the ReminderData.

Open AndroidManifest.xml and register the AlarmReceiver by adding this xml inside the application tag:

   <receiver android:name=".notif.AlarmReceiver" />

All right, you have finally made it to the last step! Now you will issue the notification for the pet’s medication.

Open the NotificationHelper class one more time, navigate to the createNotificationForPet()method and add the following code:

// 1
val groupBuilder = buildGroupNotification(context, reminderData)
// 2
val notificationBuilder = buildNotificationForPet(context, reminderData)
// 3
val administerPendingIntent = createPendingIntentForAction(context, reminderData)
notificationBuilder.addAction(
  R.drawable.baseline_done_black_24, 
  context.getString(R.string.administer), 
  administerPendingIntent)
// 4
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(reminderData.type.ordinal, groupBuilder.build())
notificationManager.notify(reminderData.id, notificationBuilder.build())

Here you:

  1. Create a group notification.
  2. Create a notification for the pet.
  3. Add an action to the notification for the pet.
  4. Called notify using NotificationManager for both notifications.

Group notifications, along with channels, help you stay organized as more notifications are introduced into your applications. They will help the notifications issued from your app group together in the notification shade. Since the sample app supports reminders for cats, dogs and other pets, you already have the groups you need to get started.

Modify the buildGroupNotification(context: Context, reminderData: ReminderData)method like below:

private fun buildGroupNotification(context: Context, reminderData: ReminderData): NotificationCompat.Builder {
  // 1
  val channelId = "${context.packageName}-${reminderData.type.name}"
  return NotificationCompat.Builder(context, channelId).apply {
    setSmallIcon(R.drawable.ic_stat_medicine)
    setContentTitle(reminderData.type.name)
    setContentText(context.getString(R.string.group_notification_for, reminderData.type.name))
    setStyle(NotificationCompat.BigTextStyle()
        .bigText(context.getString(R.string.group_notification_for, reminderData.type.name)))
    setAutoCancel(true)
    setGroupSummary(true) // 2
    setGroup(reminderData.type.name) // 3
  }
}
  1. Using the same channelId, create the group notification.
  2. Set this notification as the group summary (useful on versions of Android prior to API 24).
  3. Set the group to the pet type name (i.e. Cat, Dog, or Other).

Next, you need to create the notification using the same channelId. This time, you will also add a large icon to the notification to make it distinguishable from the other pet types.

Navigate to the buildNotificationForPet(context: Context, reminderData: ReminderData) method and modify it like below:

private fun buildNotificationForPet(context: Context, reminderData: ReminderData): NotificationCompat.Builder {
  // 1
  val channelId = "${context.packageName}-${reminderData.type.name}"
  return NotificationCompat.Builder(context, channelId).apply {
    setSmallIcon(R.drawable.ic_stat_medicine)
    setContentTitle(reminderData.name)
    setAutoCancel(true)
    // 2
    val drawable = when (reminderData.type) {
      ReminderData.PetType.Dog -> R.drawable.dog
      ReminderData.PetType.Cat -> R.drawable.cat
      else -> R.drawable.other
    }
    // 3
    setLargeIcon(BitmapFactory.decodeResource(context.resources, drawable))
    setContentText("${reminderData.medicine}, ${reminderData.desc}")
    // 4
    setGroup(reminderData.type.name)
    if (reminderData.note != null) {
      setStyle(NotificationCompat.BigTextStyle().bigText(reminderData.note))
    }
    val intent = Intent(context, MainActivity::class.java).apply {
      flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
      putExtra(ReminderDialog.KEY_ID, reminderData.id)
    }
    val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
    setContentIntent(pendingIntent)
  }
}
  1. Build the Notification using the same channelId as the group notification.
  2. Using the ReminderData get a drawable reference for the large icon.
  3. Apply the large icon to the notification.
  4. Set the notification’s group.

Actions help provide quick access to features that aren’t necessarily scoped to the in-app experience. Say, for example, you receive a notification from your email client and you either want to respond to it or archive the email. Actions can provide this behavior.

Navigate to createPendingIntentForAction() in NotificationHelper and add the following code:

// 1
val administerIntent = Intent(context, AppGlobalReceiver::class.java).apply {
  action = context.getString(R.string.action_medicine_administered)
  putExtra(AppGlobalReceiver.NOTIFICATION_ID, reminderData.id)
  putExtra(ReminderDialog.KEY_ID, reminderData.id)
  putExtra(ReminderDialog.KEY_ADMINISTERED, true)
}
// 2
return PendingIntent.getBroadcast(context, ADMINISTER_REQUEST_CODE, administerIntent, PendingIntent.FLAG_UPDATE_CURRENT)
  1. Create an Intent to launch the AppGlobalReceiver
  2. Wrap the Intent in a PendingIntent

Note: The AppGlobalReceiver has been created and registered in the AndroidManifest.xml for you.

Note: The AppGlobalReceiver has been created and registered in the AndroidManifest.xml for you.

Finally, build and re-run the application to see this all in action. When the app is running delete and reload the sample data and the alarms will schedule in the background. When the notifications display, you should see them group together and have an action to update the medicine as administered.

Note: The progression of notifications grouping, depicted over time.

Note: The progression of notifications grouping, depicted over time.

Wow! Look at you go. Now you have a fully functional application for reminding you to administer medication to your pets.