[TUT] Set a notification for a user chosen time

Here we will set a notification to be popped up at a specific time in the future that the user has chosen. We will follow on from the Basic Date Picker Tutorial to use this Date Picker to allow the user to set a reminder.

It is a quite interesting scenario and this is just a taster of what is possible, but say you have just created a todo list and you want to notify the user to do that item, you can prompt the user to select a date then have the system pop up a notification when that date comes around. When they then select your notification it will take them back into your application.

As usual all the source code below is well commented allowing you to ignore my ramblings and dive straight in. Source code files are at the bottom of the post.

What we’re going to do:

  • Create an activity to select a date in the future
  • After selecting a date, pass it to a service to deal with it
  • Create an alarm for the date selected
  • When this alarm is raised pop up a notification in the system bar
  • Clicking the notification will bring the user back into your app

So here … we .. go

First comes the MainActivity, this just shows the user a datepicker allowing them to select a date in the future that you are going to use as the alarm reminder date:
MainActivity.java

package com.blundell.tut.ui.phone;

import java.util.Calendar;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.DatePicker;
import android.widget.Toast;

import com.blundell.tut.R;
import com.blundell.tut.service.ScheduleClient;

/**
 * This is the Main Activity of our app.
 * Here we allow the user to select a date,
 * we then set a notification for that date to appear in the status bar
 *  
 * @author paul.blundell
 */
public class MainActivity extends Activity  {
	// This is a handle so that we can call methods on our service
    private ScheduleClient scheduleClient;
    // This is the date picker used to select the date for our notification
	private DatePicker picker;

	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Create a new service client and bind our activity to this service
        scheduleClient = new ScheduleClient(this);
        scheduleClient.doBindService();

        // Get a reference to our date picker
        picker = (DatePicker) findViewById(R.id.scheduleTimePicker);
    }
	
    /**
     * This is the onClick called from the XML to set a new notification 
     */
    public void onDateSelectedButtonClick(View v){
    	// Get the date from our datepicker
    	int day = picker.getDayOfMonth();
    	int month = picker.getMonth();
    	int year = picker.getYear();
    	// Create a new calendar set to the date chosen
    	// we set the time to midnight (i.e. the first minute of that day)
    	Calendar c = Calendar.getInstance();
    	c.set(year, month, day);
    	c.set(Calendar.HOUR_OF_DAY, 0);
    	c.set(Calendar.MINUTE, 0);
    	c.set(Calendar.SECOND, 0);
    	// Ask our service to set an alarm for that date, this activity talks to the client that talks to the service
    	scheduleClient.setAlarmForNotification(c);
    	// Notify the user what they just did
    	Toast.makeText(this, "Notification set for: "+ day +"/"+ (month+1) +"/"+ year, Toast.LENGTH_SHORT).show();
    }
    
    @Override
    protected void onStop() {
    	// When our activity is stopped ensure we also stop the connection to the service
    	// this stops us leaking our activity into the system *bad*
    	if(scheduleClient != null)
    		scheduleClient.doUnbindService();
    	super.onStop();
    }
}

The activity has the same layout as our previous tutorial:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <DatePicker
        android:id="@+id/scheduleTimePicker"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/selectButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onDateSelectedButtonClick"
        android:text="Set notification for this date" />

</LinearLayout>

When the above activity is created it starts a new service. This service is how we organise our alarm tasks. When you want to do a new task, you don’t want to use the UI thread, as this means the system can’t draw anything if you do. So we start a new service, bind to this service and then tell the service to set an alarm for the reminder date.

We connect to our service in two steps, using a client and a service, this allows the separation of concerns over binding to the service and managing its resources. Just remember to unbind from your service when the activity is finished.

Below is the service client, this connects to the service then passes on a reference of the service to the activity (so that we can tell the service to do stuff from the activity). It’s a middle man for communication.

ScheduleClient.java:

package com.blundell.tut.service;

import java.util.Calendar;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;

/**
 * This is our service client, it is the 'middle-man' between the
 * service and any activity that wants to connect to the service
 * 
 * @author paul.blundell
 */
public class ScheduleClient {

	// The hook into our service
	private ScheduleService mBoundService;
	// The context to start the service in
	private Context mContext;
	// A flag if we are connected to the service or not
	private boolean mIsBound;

	public ScheduleClient(Context context) {
		mContext = context;
	}
	
	/**
	 * Call this to connect your activity to your service
	 */
	public void doBindService() {
		// Establish a connection with our service
		mContext.bindService(new Intent(mContext, ScheduleService.class), mConnection, Context.BIND_AUTO_CREATE);
		mIsBound = true;
	}
	
	/**
	 * When you attempt to connect to the service, this connection will be called with the result.
	 * If we have successfully connected we instantiate our service object so that we can call methods on it.
	 */
	private ServiceConnection mConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {
			// This is called when the connection with our service has been established, 
			// giving us the service object we can use to interact with our service.
			mBoundService = ((ScheduleService.ServiceBinder) service).getService();
		}

		public void onServiceDisconnected(ComponentName className) {
			mBoundService = null;
		}
	};

	/**
	 * Tell our service to set an alarm for the given date
	 * @param c a date to set the notification for
	 */
	public void setAlarmForNotification(Calendar c){
		mBoundService.setAlarm(c);
	}
	
	/**
	 * When you have finished with the service call this method to stop it 
	 * releasing your connection and resources
	 */
	public void doUnbindService() {
		if (mIsBound) {
			// Detach our existing connection.
			mContext.unbindService(mConnection);
			mIsBound = false;
		}
	}
}

The ScheduleClient talks to the ScheduleService, within the ScheduleService we call our task, the task runs on another thread so that the service can run the task then return immediately.

ScheduleService.java

package com.blundell.tut.service;

import java.util.Calendar;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.blundell.tut.service.task.AlarmTask;

public class ScheduleService extends Service {

	/**
	 * Class for clients to access
	 */
	public class ServiceBinder extends Binder {
		ScheduleService getService() {
			return ScheduleService.this;
		}
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i("ScheduleService", "Received start id " + startId + ": " + intent);
		
		// We want this service to continue running until it is explicitly stopped, so return sticky.
		return START_STICKY;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	// This is the object that receives interactions from clients. See
	private final IBinder mBinder = new ServiceBinder();

	/**
	 * Show an alarm for a certain date when the alarm is called it will pop up a notification
	 */
	public void setAlarm(Calendar c) {
		// This starts a new thread to set the alarm
		// You want to push off your tasks onto a new thread to free up the UI to carry on responding
		new AlarmTask(this, c).run();
	}
}

The Schedule service starts our AlarmTask. Within the Alarm Task we set a reminder for the Android system to inform us when that future date is hit. For this we use the systems AlarmManager service, the AlarmManager takes an intent to fire at this date. Our intent starts another service called the NotifyService. The NotifyService is how we will show a notification. The only caveat with the system AlarmManager is if the phone is turned off and on your alarm is lost. Quickly thinking, the way round this would be to have your app be notified of the phone starting up and re-starting your alarms.

AlarmTask.java

package com.blundell.tut.service.task;

import java.util.Calendar;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

import com.blundell.tut.service.NotifyService;

/**
 * Set an alarm for the date passed into the constructor
 * When the alarm is raised it will start the NotifyService
 * 
 * This uses the android build in alarm manager *NOTE* if the phone is turned off this alarm will be cancelled
 * 
 * This will run on it's own thread.
 * 
 * @author paul.blundell
 */
public class AlarmTask implements Runnable{
	// The date selected for the alarm
	private final Calendar date;
	// The android system alarm manager
	private final AlarmManager am;
	// Your context to retrieve the alarm manager from
	private final Context context;

	public AlarmTask(Context context, Calendar date) {
		this.context = context;
		this.am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
		this.date = date;
	}
	
	@Override
	public void run() {
		// Request to start are service when the alarm date is upon us
		// We don't start an activity as we just want to pop up a notification into the system bar not a full activity
		Intent intent = new Intent(context, NotifyService.class);
		intent.putExtra(NotifyService.INTENT_NOTIFY, true);
		PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
		
		// Sets an alarm - note this alarm will be lost if the phone is turned off and on again
		am.set(AlarmManager.RTC, date.getTimeInMillis(), pendingIntent);
	}
}

When the Android system fires our alarm, the intent we set starts our NotifyService. This NotifyService is pretty similar in setup to our ScheduleService, however because we don’t want to bind the service to an activity we just start the service up (not using a client), then call our reminder method and shut it down again. The notification will pop into the system menu informing the user. Notifications can have different properties like, sounds, text, images. You’ll find a whole chapter on them here.

NotifyService.java

package com.blundell.tut.service;

import android.R;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.blundell.tut.ui.phone.SecondActivity;

/**
 * This service is started when an Alarm has been raised
 * 
 * We pop a notification into the status bar for the user to click on
 * When the user clicks the notification a new activity is opened
 * 
 * @author paul.blundell
 */
public class NotifyService extends Service {

	/**
	 * Class for clients to access
	 */
	public class ServiceBinder extends Binder {
		NotifyService getService() {
			return NotifyService.this;
		}
	}

	// Unique id to identify the notification.
	private static final int NOTIFICATION = 123;
	// Name of an intent extra we can use to identify if this service was started to create a notification	
	public static final String INTENT_NOTIFY = "com.blundell.tut.service.INTENT_NOTIFY";
	// The system notification manager
	private NotificationManager mNM;

	@Override
	public void onCreate() {
		Log.i("NotifyService", "onCreate()");
		mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i("LocalService", "Received start id " + startId + ": " + intent);
		
		// If this service was started by out AlarmTask intent then we want to show our notification
		if(intent.getBooleanExtra(INTENT_NOTIFY, false))
			showNotification();
		
		// We don't care if this service is stopped as we have already delivered our notification
		return START_NOT_STICKY;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	// This is the object that receives interactions from clients
	private final IBinder mBinder = new ServiceBinder();

	/**
	 * Creates a notification and shows it in the OS drag-down status bar
	 */
	private void showNotification() {
		// This is the 'title' of the notification
		CharSequence title = "Alarm!!";
		// This is the icon to use on the notification
		int icon = R.drawable.ic_dialog_alert;
		// This is the scrolling text of the notification
		CharSequence text = "Your notification time is upon us.";		
		// What time to show on the notification
		long time = System.currentTimeMillis();
		
		Notification notification = new Notification(icon, text, time);

		// The PendingIntent to launch our activity if the user selects this notification
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, SecondActivity.class), 0);

		// Set the info for the views that show in the notification panel.
		notification.setLatestEventInfo(this, title, text, contentIntent);

		// Clear the notification when it is pressed
		notification.flags |= Notification.FLAG_AUTO_CANCEL;
		
		// Send the notification to the system.
		mNM.notify(NOTIFICATION, notification);
		
		// Stop the service when we are finished
		stopSelf();
	}
}

And thats it, we have our notification! When this notification is pressed it starts a new activity bringing the user back into your app.

SecondActivity.java

package com.blundell.tut.ui.phone;

import android.app.Activity;
import android.os.Bundle;

import com.blundell.tut.R;

/**
 * This is the activity that is started when the user presses the notification in the status bar
 * @author paul.blundell
 */
public class SecondActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_second);
	}
	
}

Don’t forget to add your two activitys and two services to the manifest. Enjoy setting reminders!

AndroidManifest.java

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.blundell.tut"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".ui.phone.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ui.phone.SecondActivity" />

        <service android:name=".service.ScheduleService" />
        <service android:name=".service.NotifyService" />
    </application>

</manifest>

Here is the NotificationTut Eclipse project Source code.

Say thanks if you’ve found this useful!

136 thoughts on “[TUT] Set a notification for a user chosen time

Comments are closed.