This practical codelab is part of Unit 3: Working in the background in the Android Developer Fundamentals (Version 2) course. You will get the most value out of this course if you work through the codelabs in sequence:

Introduction

In previous lessons, you learned how to make your app respond when a user taps a button or a notification. You also learned how to make your app respond to system events using broadcast receivers. But what if your app needs to take action at a specific time, for example for a calendar notification? In this case, you would use AlarmManager. The AlarmManager class lets you launch and repeat a PendingIntent at a specified time, or after a specified interval.

In this practical, you create a timer that reminds the user to stand up every 15 minutes.

What you should already know

You should be able to:

What you'll learn

What you'll do

Stand Up! is an app that helps you stay healthy by reminding you to stand up and walk around every 15 minutes. It uses a notification to let you know when 15 minutes have passed. The app includes a toggle button that can turn the alarm on and off.

1.1 Create the Stand Up! project layout

  1. In Android Studio, create a new project called "Stand Up!". Accept the default options and use the Empty Activity template.
  2. Open the activity_main.xml layout file. Replace the "Hello World" TextView with the following ToggleButton:

Attribute

Value

android:id

"@+id/alarmToggle"

android:layout_width

"wrap_content"

android:layout_height

"wrap_content"

android:textOff

"Alarm off"

android:textOn

"Alarm on"

app:layout_constraintStart_toStartOf

"parent"

app:layout_constraintBottom_toBottomOf

"parent"

app:layout_constraintEnd_toEndOf

"parent"

app:layout_constraintTop_toTopOf

"parent"

  1. Extract your string resources.

1.2 Set up the setOnCheckedChangeListener() method

The Stand Up! app includes a toggle button that is used to set and cancel the alarm, as well as to visually represent the alarm's status. To set the alarm when the toggle is turned on, your app uses the onCheckedChangeListener() method.

In MainActivity.java, inside the onCreate() method, implement the following steps:

  1. Find the ToggleButton by id.
ToggleButton alarmToggle = findViewById(R.id.alarmToggle);
  1. Call setOnCheckedChangeListener() on the ToggleButton instance, and begin entering "new OnCheckedChangeListener". Android Studio autocompletes the method for you, including the required onCheckedChanged() override method.

The first parameter in onCheckedChanged() is the CompoundButton that the user tapped, which in this case is the alarm ToggleButton. The second parameter is a boolean that represents the state of the ToggleButton, that is, whether the toggle is on or off.

alarmToggle.setOnCheckedChangeListener(
   new CompoundButton.OnCheckedChangeListener() {
   @Override
   public void onCheckedChanged(CompoundButton compoundButton, 
      boolean isChecked) {
   }
});
  1. In the onCheckedChanged() method, set up an if-else block using the boolean parameter. If the alarm was turned on or off, display a Toast message to the user.
   String toastMessage;
   if(isChecked){
       //Set the toast message for the "on" case.
       toastMessage = "Stand Up Alarm On!";
   } else {
       //Set the toast message for the "off" case.
       toastMessage = "Stand Up Alarm Off!";
   }

   //Show a toast to say the alarm is turned on or off.
   Toast.makeText(MainActivity.this, toastMessage,Toast.LENGTH_SHORT)
           .show();
  1. Extract your string resources.

The next step is to create the notification that reminds the user to stand up every 15 minutes. For now, the notification is delivered immediately when the toggle is set.

2.1 Create the notification

In this step, you create a deliverNotification() method that posts the reminder to stand up and walk.

Implement the following steps in MainActivity.java:

  1. Create a member variable called mNotificationManager of the type NotificationManager.
private NotificationManager mNotificationManager;
  1. In the onCreate() method, initialize mNotificationManager using getSystemService().
mNotificationManager = (NotificationManager) 
        getSystemService(NOTIFICATION_SERVICE);
  1. Create member constants for the notification ID and the notification channel ID. You will use these to display the notification. To learn more about notifications, see the Notifications overview.
private static final int NOTIFICATION_ID = 0;
private static final String PRIMARY_CHANNEL_ID =
       "primary_notification_channel";

Create a notification channel

For Android 8.0 (API level 27) and higher, to display notifications to the user, you need a notification channel.

Create a notification channel:

  1. Create a method called createNotificationChannel().
  2. Call createNotificationChannel() at the end of onCreate().
/**
* Creates a Notification channel, for OREO and higher.
*/
public void createNotificationChannel() {

   // Create a notification manager object.
   mNotificationManager =
           (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

   // Notification channels are only available in OREO and higher.
   // So, add a check on SDK version.
   if (android.os.Build.VERSION.SDK_INT >=
           android.os.Build.VERSION_CODES.O) {

       // Create the NotificationChannel with all the parameters.
       NotificationChannel notificationChannel = new NotificationChannel
               (PRIMARY_CHANNEL_ID,
                       "Stand up notification",
                       NotificationManager.IMPORTANCE_HIGH);

       notificationChannel.enableLights(true);
       notificationChannel.setLightColor(Color.RED);
       notificationChannel.enableVibration(true);
       notificationChannel.setDescription
               ("Notifies every 15 minutes to stand up and walk");
       mNotificationManager.createNotificationChannel(notificationChannel);
   }
}

Set the notification content Intent

  1. Create a method called deliverNotification() that takes the Context as an argument and returns nothing.
private void deliverNotification(Context context) {}
  1. In the deliverNotification() method, create an Intent that you will use for the notification content intent.
Intent contentIntent = new Intent(context, MainActivity.class);
  1. In the deliverNotification() method, after the definition of contentIntent, create a PendingIntent from the content intent. Use the getActivity() method, passing in the notification ID and using the FLAG_UPDATE_CURRENT flag:
PendingIntent contentPendingIntent = PendingIntent.getActivity
       (context, NOTIFICATION_ID, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT);

Add a notification icon and build the notification

  1. Use the Image Asset Studio to add an image asset to use as the notification icon. Choose any icon you find appropriate for this alarm and name it ic_stand_up. For example, you could use the directions "walk" icon:
  2. In the deliverNotification() method, use the NotificationCompat.Builder to build a notification using the notification icon and content intent. Set notification priority and other options.
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, PRIMARY_CHANNEL_ID)
       .setSmallIcon(R.drawable.ic_stand_up)
       .setContentTitle("Stand Up Alert")
       .setContentText("You should stand up and walk around now!")
       .setContentIntent(contentPendingIntent)
       .setPriority(NotificationCompat.PRIORITY_HIGH)
       .setAutoCancel(true)
       .setDefaults(NotificationCompat.DEFAULT_ALL);
  1. Extract your string resources.

Deliver the notification and test your app

  1. At the end of the deliverNotification() method, use the NotificationManager to deliver the notification:
mNotificationManager.notify(NOTIFICATION_ID, builder.build());
  1. In onCreate(), call deliverNotification() when the alarm toggle button is turned on, passing in the activity context.
  2. In onCreate(), call cancelAll() on the NotificationManager if the toggle is turned off to remove the notification.
if(isChecked){
   deliverNotification(MainActivity.this);
   //Set the toast message for the "on" case
   toastMessage = "Stand Up Alarm On!";
} else {
   //Cancel notification if the alarm is turned off
   mNotificationManager.cancelAll();

   //Set the toast message for the "off" case
   toastMessage = "Stand Up Alarm Off!";
}
  1. Run the app, and check that the notification is delivered.

At this point there is no alarm at all: the notification is immediately delivered when the alarm toggle button is turned on. In the next task you implement the AlarmManager to schedule and deliver the notification every 15 minutes.

Now that your app can send a notification, it's time to implement the main component of your app: the AlarmManager. This class will periodically deliver the reminder to stand up. AlarmManager has many kinds of alarms built into it, both one-time and periodic, exact and inexact. To learn more about the different kinds of alarms, see Schedule repeating alarms.

AlarmManager, like notifications, uses a PendingIntent that it delivers with the specified options. Because of this, AlarmManager can deliver the Intent even when the app is no longer running.

A broadcast receiver receives the broadcast intent and delivers the notification.

Alarms do not fire when the device is in Doze mode (idle). Instead, alarms are deferred until the device exits Doze. To guarantee that alarms execute, you can use setAndAllowWhileIdle() or setExactAndAllowWhileIdle(). You can also use the new WorkManager API, which is built to perform background work either once or periodically. For details, see Schedule tasks with WorkManager.

The AlarmManager can trigger one-time or recurring events that occur even when your app is not running. For real-time clock (RTC) alarms, schedule events using System.currentTimeMillis(). For elapsed-time (ELAPSED_REALTIME) alarms, schedule events using elapsedRealtime(). Deliver a PendingIntent when events occur.

For more about the available clocks and how to control the timing of events, see SystemClock.

3.1 Create the broadcast receiver

Create a broadcast receiver that receives the broadcast intents from the AlarmManager and reacts appropriately:

  1. In Android Studio, select File > New > Other > Broadcast Receiver.
  2. Enter AlarmReceiver for the Class Name. Make sure that the Exported checkbox is cleared so that other apps can't invoke this broadcast receiver.

Android Studio creates a subclass of BroadcastReceiver with the required method, onReceive(). Android Studio also adds the receiver to your AndroidManifest file.

Implement the following steps in broadcast receiver's AlarmReceiver.java file:

  1. Remove the entire default implementation from the onReceive() method, including the line that raises the UnsupportedOperationException.
  2. Cut and paste the deliverNotification() method from the MainActivity class to the AlarmReceiver class and call it from onReceive(). You may notice some variables highlighted in red. You define them in the next step.
  3. Copy the NOTIFICATION_ID, PRIMARY_CHANNEL_ID, and mNotificationManager member variables from the MainActivity class into the AlarmReceiver class.
private NotificationManager mNotificationManager;
private static final int NOTIFICATION_ID = 0;

// Notification channel ID.
private static final String PRIMARY_CHANNEL_ID =
       "primary_notification_channel";
  1. Initialize the mNotificationManager variable at the beginning of the onReceive() method. You have to call getSystemService() from the passed-in context:
@Override
public void onReceive(Context context, Intent intent) {
mNotificationManager = (NotificationManager) 
              context.getSystemService(Context.NOTIFICATION_SERVICE);
   deliverNotification(context);
}

3.2 Set up the broadcast pending intent

The AlarmManager is responsible for delivering the PendingIntent at a specified interval. This PendingIntent delivers an intent letting the app know it is time to update the remaining time in the notification.

Implement the following steps in MainActivity.java, inside onCreate():

  1. Create an Intent called notifyIntent. Pass in the context and AlarmReceiver class.
Intent notifyIntent = new Intent(this, AlarmReceiver.class);
  1. Create the notify PendingIntent. Use the context, the NOTIFICATION_ID variable, the new notify intent, and the FLAG_UPDATE_CURRENT flag.
PendingIntent notifyPendingIntent = PendingIntent.getBroadcast
       (this, NOTIFICATION_ID, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);

3.3 Set the repeating alarm

Now you use the AlarmManager to deliver the broadcast every 15 minutes. For this task, the appropriate type of alarm is a`n inexact, repeating alarm that uses elapsed time and wakes the device up if it is asleep. The real-time clock is not relevant here, because you want to deliver the notification every 15 minutes.

Implement the following steps in MainActivity.java:

  1. Initialize the AlarmManager in onCreate() by calling getSystemService().
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
  1. In the onCheckedChanged() method, remove the call to deliverNotification().
  2. In the onCheckedChanged() method, call setInexactRepeating() on the alarm manager instance inside the if case (when the alarm is toggled on).

You use the setInexactRepeating() alarm because it is more resource-efficient to use inexact timing, which lets the system bundle alarms from different apps together. Also, it's acceptable for your app to deviate a little bit from the exact 15-minute interval.

The setInexactRepeating() method takes four arguments:

long repeatInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
long triggerTime = SystemClock.elapsedRealtime()
       + repeatInterval;

//If the Toggle is turned on, set the repeating alarm with a 15 minute interval
if (alarmManager != null) {
   alarmManager.setInexactRepeating
       (AlarmManager.ELAPSED_REALTIME_WAKEUP,
       triggerTime, repeatInterval, notifyPendingIntent);
}
  1. Inside the else case (when the alarm is toggled off), cancel the alarm by calling cancel() on the AlarmManager. Pass in the pending intent used to create the alarm.
if (alarmManager != null) {
    alarmManager.cancel(notifyPendingIntent);
}

Keep the call to cancelAll() on the NotificationManager, because turning the alarm toggle off should still remove any existing notification.

The AlarmManager now delivers your broadcast 15 minutes after the alarm is set, and every 15 minutes after that.

  1. Run your app. If you don't want to wait 15 minutes to see the notification, change the trigger time to SystemClock.elapsedRealtime() to see the notification immediately. You can also change the interval to a shorter time to make sure that the repeated alarm is working.

You now have an app that can schedule and perform a repeated operation, even if the app is no longer running. Go ahead, exit the app completely—the notification is still delivered.

You still need to fix one thing to ensure a proper user experience: if the app is closed, the toggle button resets to the off state, even if the alarm has already been set. To fix this, you need to check the state of the alarm every time the app is launched.

3.4 Check the state of the alarm

To track the state of the alarm, you need a boolean variable that is true if the alarm exists, and false otherwise. To set this boolean, you can call PendingIntent.getBroadcast() with the FLAG_NO_CREATE flag. If a PendingIntent exists, that PendingIntent is returned; otherwise the call returns null.

Implement the following steps in MainActivity.java:

  1. Create a boolean that is true if PendingIntent is not null, and false otherwise. Use this boolean to set the state of the ToggleButton when your app starts. This code has to come before the PendingIntent is created. (Otherwise it always returns true.)
boolean alarmUp = (PendingIntent.getBroadcast(this, NOTIFICATION_ID, notifyIntent,
       PendingIntent.FLAG_NO_CREATE) != null);
  1. Set the state of the toggle right after you define alarmUp:
alarmToggle.setChecked(alarmUp);

This ensures that the toggle is always on if the alarm is set, and off otherwise. You now have a repeated scheduled alarm to remind the user to stand up every 15 minutes.

  1. Run your app. Switch on the alarm. Exit the app. Open the app again. The alarm button shows that the alarm is on.

Android Studio project: StandUp

The AlarmManager class also handles the usual kind of alarm clocks, the kind that wake you up in the morning. On devices running API 21 and higher, you can get information about the next alarm clock of this kind by calling getNextAlarmClock() on the alarm manager.

Add a button to your app that displays a Toast message. The toast shows the time of the next alarm clock that the user has set.

The related concept documentation is in 8.2: Alarms.

Android developer documentation:

Other resources:

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Make an app that delivers a notification when the time is 11:11 AM. The screen displays a toggle switch that turns the alarm on and off.

Answer these questions

Question 1

In which API level did inexact timing become the default for AlarmManager? (All set() methods use inexact timing, unless explicitly stated.)

Submit your app for grading

Guidance for graders

Check that the app has the following features:

To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).

For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).