Notifications

Intro

You've already seen one way to provide notification to users in the form of Toast messages.  While handy, Toasts are limited in terms of both the limited amount of time they're displayed and the fact that the user must have the application running.  When you need your app to provide notifications to the user that persist until a specific action occurs (such as the user clicking on the notification), or when your app is not running, then status bar notifications are the way to go!

Basic Notifications

Like alarms, status bar notifications are relatively simple, requiring only a few steps to get up and running:

1.  Get an instance of the system's Notification Manager:

NotificationManager notificationManager = (NotificationManager) context
		.getSystemService(Context.NOTIFICATION_SERVICE);

2.  Create a new Notification.  For greatest compatability across android versions, we'll use the Notification Builder provided by the android support library:

NotificationCompat.Builder mBuilder =new NotificationCompat.Builder(context)
        .setSmallIcon(icon)
	.setContentTitle(title)
	.setContentText(message);

3.  Create an Intent to specify which activity to launch when the notification is clicked on.  (note: this can also be a null value if you don't want the notification to be used to launch an Activity).

Intent notificationIntent = new Intent(context, MainActivity.class);

4.  Create a PendingIntent, passing the Intent created above, allowing the system to run the Intent with our application's permissions.  Then, tell the Notification Builder to use the Pending Intent for our notification:

PendingIntent intent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
mBuilder.setContentIntent(intent);

5.  All the pieces are now in place!  Now it's just a matter of telling the Notification Manager to display the Notification:

notificationManager.notify(notificationId, mBuilder.build());

Demonstration

In order to demonstrate the notification at work, were going to modify the application you created during the Alarms lesson.

 

That should do it. Now run your app. When you press the button labelled "Create Notification", a notification will appear in the taskbar. To remove the notification, click on the button labelled "Clear Notification".

Improvements

Prevent a New Activity From Being Created 

Activity lifecycle management never ends!  Remember that when creating your notification, you had to create an Intent to launch the associated Activity.  If you don't explicitly state otherwise, whenever you click on the notification, a new instance of the activity (with its own lifecycle) is created.  To prevent the notification from creating a new activity, set the proper flags:

// set intent so it does not start a new activity
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
	| Intent.FLAG_ACTIVITY_SINGLE_TOP);
IMPORTANT:  Flags must be set before you assign the Intent to a PendingIntent!

 

There are many options for specifying how to PendingIntent handles the creation of an Activity.  To see all intent flag options, see here: Intent flag options

 

Auto-Cancelling a Notification

The way your sample app is currently written, the only way to remove the notification from the status bar is to manually click the button that we created, which in turn programatically cancels the notification.  This process can be automated by setting the notification to cancel itself once selected:

// set notification to cancel itself when selected 
// as opposed to canceling it manually
mBuilder.setAutoCancel(true);

 

Other Improvements:

Customization

Using a Custom Notification Sound

Custom Vibration Patterns

// customize vibration pattern
// delay 50ms delay, vibrate 600ms, pause 500ms, vibrate 1200ms
mBuilder.setVibrate(new long[] { 50, 600, 500, 1200});

Triggering via an Alarm

It's time to tie together the previous lesson and this one.  We'll modify the code so that notifications can be triggered from an Alarm.  In order to do this and also keep our code organized nicely, we'll create a new java class to handle Notifications.  In Eclipse, create a new custom class called Notifier.java, and structure it like this:

public class Notifier {
	
    private final static String TAG = "PEPIN";
	
    public static void generateNotification(Context context, String title,
		String message, int notificationId) {
	int icon = R.drawable.ic_launcher;
		
	// get instance of notification manager
	NotificationManager notificationManager = (NotificationManager) context
			.getSystemService(Context.NOTIFICATION_SERVICE);
		
	NotificationCompat.Builder mBuilder =
	        new NotificationCompat.Builder(context)
	        .setSmallIcon(icon)
	        .setContentTitle(title)
	        .setContentText(message);

	// intent to specify which activity to launch when the 
	// notification is selected
	Intent notificationIntent = new Intent(context,
			MainActivity.class);
		
	// set intent so it does not start a new activity
	notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
			| Intent.FLAG_ACTIVITY_SINGLE_TOP);
		
	// pending intent to allow the system to launch the activity
	// inside our app from the notification
	PendingIntent intent = PendingIntent.getActivity(context, 0,
			notificationIntent, 0);
		
	mBuilder.setContentIntent(intent);
		
        // set notification to cancel itself when selected 
	// as opposed to canceling it manually
	mBuilder.setAutoCancel(true);
		
	mBuilder.setTicker("ticker message here");

	// set to play default notification sound
//	mBuilder.setDefaults(Notification.DEFAULT_SOUND);
		
	// set to vibrate if vibrate is enabled on device
//	mBuilder.setDefaults(Notification.DEFAULT_VIBRATE);
		
	// set lights
//	mBuilder.setDefaults(Notification.DEFAULT_LIGHTS);
		
	// set to use default sound and vibrate
//	mBuilder.setDefaults(Notification.DEFAULT_SOUND | 
//                Notification.DEFAULT_VIBRATE);
		
	// set to use default sound, vibrate and lights all at once
//	mBuilder.setDefaults(Notification.DEFAULT_ALL);

	// set to play your own sound file
	mBuilder.setSound(Uri.parse("android.resource://" +
			   context.getPackageName() + "/raw/lefreak"));
		
	// customize vibration pattern
	// delay 50ms delay, vibrate 600ms, pause 500ms, vibrate 1200ms
	mBuilder.setVibrate(new long[] { 50, 600, 500, 1200});
		
	notificationManager.notify(notificationId, mBuilder.build());
    }
	
    public static void cancelNotification(Context context, int notificationId) {
	try{
   	    NotificationManager notificationManager = (NotificationManager) context
			.getSystemService(Context.NOTIFICATION_SERVICE);
	    notificationManager.cancel(notificationId);
	} catch (Exception e) {
            Log.i(TAG, "cancelNotification() error: " + e.getMessage());
	}
    }
	
}

*Note: The reason why I had you make the two methods static way back at the beginning of the lesson should now be apparent.  Because the methods we added to the Notifier class are static, there is no need to create an instance of the Notifier in order to use them.  The Notifier class exists for one purpose: create and cancel notifications.  This is an example of the "Single Responsibility Principle".  

Modify the MainActivity.java file so that the new class is used:

public void setNotification(View view){
	Notifier.generateNotification(this, "Title Text", 
            "Your First Notification", NOTIFICATION_ID);
}

public void clearNotification(View view){
	Notifier.cancelNotification(this, NOTIFICATION_ID);
}

Run the app again.  At this point, it should function exactly the same.  If it does not, then go back and make sure you haven't made any mistakes.  

If everything is good, then there's one final change to make.  Modify the ServiceReceivingAlarm.java file so that the notification is triggered in the onHandleIntent() method:

@Override
protected void onHandleIntent(Intent intent) {
	Log.i("PEPIN", "Service started by alarm!!");
	Notifier.generateNotification(getBaseContext(), 
            "Service Launched Notification", 
	    "This notification launched by your service", 
            1775);
}

Now the notification is launched by clicking on the buttons as before, but also when the alarm is triggered.  Test it out by clicking the button to set an alarm.