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!
Thanks for the tutorial
This is deprecated -_-
Thank you very much