Triggering Alarms Tutorial for Android: Getting Started

Learn how to set up alarms in your Android apps using the AlarmManager API, and find out about the exact and inexact alarm types as well as best practices. By Denis Buketa.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Defining an Alarm Action

Now that all requirements for using exact alarm APIs are implemented, it’s time to implement an actual alarm.

In ExactAlarmBroadcastReceiver.kt, replace TODO (5) with:

class ExactAlarmBroadcastReceiver : BroadcastReceiver() {
  // 1
  override fun onReceive(context: Context, intent: Intent) {
    // 2
    showNotification(
        context,
        NOTIFICATION_CHANNEL_ID,
        NOTIFICATION_CHANNEL_NAME,
        NOTIFICATION_ID,
        "Time to study!"
    )
    (context.applicationContext as StuddyApplication).apply {
      exactAlarms.clearExactAlarm()
      alarmRingtoneState.value = playRingtone(context)
    }
  }
}

With this code:

  1. You defined the action that the app will execute once the alarm fires. You created a custom BroadcastReceiver class. It has only one method, which is necessary for each BroadcastReceiver.
  2. Once the BroadcastReceiver receives an event, it shows a notification, removes the running alarm, and starts playing the ringtone.

Next, open AndroidManifest.xml and find TODO (6) below the activity tag. Replace it with:

<receiver android:name="com.yourcompany.android.studdy.alarm.ExactAlarmBroadcastReceiver" />

Here, you declared your custom class as BrodcastReceiver to the system. The system must know this info to be able to respond appropriately.

Now, you need to define a method for PendingIntent needed to schedule an alarm.

Navigate to ExactAlarmsImpl.kt. At the bottom of the class, replace TODO (7) with:

private fun createExactAlarmIntent(): PendingIntent {
  // 1
  val intent = Intent(context, ExactAlarmBroadcastReceiver::class.java).  
  // 2  
  return PendingIntent.getBroadcast(
    context,
    EXACT_ALARM_INTENT_REQUEST_CODE,
    intent,
    PendingIntent.FLAG_IMMUTABLE
  )
}

Here’s a breakdown:

  1. You defined an Intent with the ExactAlarmBroadcastReceiver you created before.
  2. The return statement builds the PendingIntent needed to schedule an alarm. PendingIntent receives context, a private request code for the sender and the intent. The last argument is FLAG_IMMUTABLE, a flag indicating that the created PendingIntent should be immutable. In most cases for scheduling alarms, there’s no need to allow updating PendingIntent.

Scheduling an Alarm

Your app now correctly handles special app access and defines the action that’s executed when the alarm is triggered. The next step is to write logic for scheduling exact alarms.

Before doing that, ensure you switch back on the Allow setting alarms and reminders option.

Go to ExactAlarmsImpl.kt and look for setExactAlarmSetAlarmClock(). Notice it contains TODO (8). Replace the TODO with the following code:

// 1
val pendingIntent = createExactAlarmIntent()
// 2
val alarmClockInfo = AlarmManager.AlarmClockInfo(triggerAtMillis, pendingIntent)
// 3
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent)

Two things happened here:

  1. You used createExactAlarmIntent() to create a PendingIntent, which defines the action the app executes when the alarm is triggered.
  2. You created an AlarmClockInfo object that specifies what time to trigger the alarm.
  3. Using setAlarmClock() of AlarmManager, you scheduled the alarm.

By scheduling an alarm this way, the system invokes an alarm at a precise time in the future. This kind of alarm is highly visible to users and, to ensure absolute precision, the system won’t modify the delivery time. The system will even leave low-power modes, if necessary, to deliver exact alarms.

Note: To learn more about low-power modes, take a look at the Doze documentation.

Build and run the app. Enter a time in the future, and tap Set. The quickest way to test the alarm is to schedule it to trigger a minute or two from the current device time. Once the alarm is triggered, stop the ringtone by tapping Stop Alarm or by swiping off the notification.

Exact Alarm is Triggered

Note: If you’re using an emulator, make sure the time on the emulator matches the time on your system. If you’ve been running your emulator for a while and time has gone out of sync, just restart your emulator.

But what if it’s not necessary to trigger the alarm immediately? Then, you should use setExact().

In setExactAlarmSetExact(), replace TODO (9) with the following code:

// 1
val pendingIntent = createExactAlarmIntent()
// 2
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent)

With these lines:

  1. You also schedule an exact alarm. You use the same createExactAlarmIntent() method to create a PendingIntent.
  2. Instead of setAlarmClock(), you use setExact() to schedule an alarm.
    By passing AlarmManager.RTC_WAKEUP for an alarm type, you declared that the system should invoke an alarm at a nearly precise time in the future. The device will wake up and fire a pending intent. Keep in mind that if a device is in a low-power mode, the system won’t invoke an alarm.

If an action that you want to trigger isn’t time-critical, you should use this method to set exact alarms.

To test this, in scheduleExactAlarm() replace the setExactAlarmSetAlarmClock() call with this method:

setExactAlarmSetExact(exactAlarm.triggerAtMillis)

Handling Idle State

Surely you’re wondering how to ensure an alarm invokes on the device in low-power mode. The solution is using setExactAndAllowWhileIdle().

In setExactAlarmSetExactAndAllowWhileIdle(), replace TODO (10) with the following lines:

val pendingIntent = createExactAlarmIntent()
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent)

This code is very similar to the previous example, but instead of setExact(), you use setExactAndAllowWhileIdle(). Just like before, the system invokes an alarm at a moment very close to the specified time in the future, but this time, it invokes an alarm even if the system is in a low-battery mode.

Understanding Alarm Types

In the previous section, you used AlarmManager.RTC_WAKEUP for an alarm type when scheduling an exact alarm. There are four exact alarm types in total:

  • ELAPSED_REALTIME: The system considers the time since your device booted in calculating when to fire a pending intent. The system doesn’t wake the device for this alarm. The time during which the device was asleep is also taken into account.
  • ELAPSED_REALTIME_WAKEUP: This is similar to previous type, but the system wakes the device to fire the pending intent.
  • RTC: Fires the pending intent at the specified time but doesn’t wake the device.
  • RTC_WAKEUP: At the specified time, the system fires an alarm.

Canceling an Alarm

Now that you know how to schedule alarm, you’ll learn how to cancel one. In clearExactAlarm(), replace TODO (11) with:

// 1
val pendingIntent = createExactAlarmIntent()
alarmManager.cancel(pendingIntent)

// 2
sharedPreferences.clearExactAlarm()
exactAlarmState.value = ExactAlarm.NOT_SET

Here is an explanation:

  1. This code first creates a PendingIntent that you use to schedule an alarm. Then, it calls cancel() on the AlarmManager and passes that PendingIntent.This action cancels the scheduled alarm.
  2. You clear the alarm data from the shared preferences and update the exactAlarmState.

Congrats! You learned a lot about exact alarms. Next, you’ll try out inexact alarms.