Tuesday 20 September 2016

How to Schedule Notification in Android using Xamarin - Part1 (Broadcast Receiver)

How to Schedule Notification in Android using Xamarin, Broadcast Receiver and Alarm Manager.
Note: I've split the topic into 3 parts.  

My objectives:
Eventually, I want to schedule local notification in both Android and iOS using Xamarin,Form.

Here are some of my learning steps before I can reach my final goal.
  1. Learn how to setup local notification for both iOS and Android. I've done push notification for both iOS and Android before. To be frank, Android is the one that usually need to spend more time for research, develop and bug fixing.
  2. Try to build local notification in Android 
  3. Try to understand how to pass param to pending intent.
  4. Try to use Alarm Manager as a scheduler, so that it will appear in the future.
  5. Trying to use Broadcast receiver.
  6. Change from Broadcast receiver to WakefulBroadcast receiver.
  7. To cover the flow when user reset the device (turn off and on again [Not the lock]).


Using Local Notification in Xamarin.Android
I started with the Walkthrough - Using Local Notifications in Xamarin.Android

It looks straight forward by 'reading' the web page. However, you may encounter some issues on some packages in your project:
  • 'Xamarin.Android.Support.v4'
  • 'Xamarin.Android.Support.v7.AppCompat'

Without update you may hit either 40 or 50+ error when you try to build the project due to something related to the Android.Support.v4.App, etc.  Or,.. you may hit around 9 errors, etc.

Well, after an update to those packages to: "Version 23.4.0.1", problem resolved. But, make sure you clean your projects and solution first.



Important hints!
If you are using Xamarin, then you have to:
[BroadcastReceiver] 
In the class that contain the extend to 'BroadcastReceiver', you need to have [BroadcastReceiver] attribute. Otherwise, it wouldn't hit the OnReceive()'.

Notification Icon
However, the notification itself will still not appearing if your notification is without icon. It wouldn't show up until you set an icon.

Alarm Manager & permission
In order to schedule your local notification, you may use AlarmManager.
However, don't forget to set the permission for Alarm. Required permission:  SET_ALARM

AlarmManager is a bit tricky. A lot of articles about alarm manager not fire or not trigger can be found in Internet. It is either no permission being set, or the something wrong on the Set() method.
Here is my example that work in my testing app. A small delay of 10 seconds before fire:

var pendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent, PendingIntentFlags.CancelCurrent);
AlarmManager alarmManager = (AlarmManager)this.GetSystemService(Context.AlarmService);
alarmManager.Set(AlarmType.ElapsedRealtime, System.Clock.ElapsedRealTime() + 10 * 1000, pendingIntent);

BroadcastReceiver vs WakefulBroadcastReceiver
When you have GetBroadcast() method set in your alarmManager, basically, you have a class that extend the BroadcastReceiver to handle the message. However, due to the reason you want to handle this even though you app is not in the foreground or the user has LOCKED the device, then you might need different broadcast.
Here you go (free pages from google book,. But it does what I want) .

WakefulBroadcast
In order to use this type of broadcast (not to let your CPU to sleep), you required to set this permission: WakeLock

If you want the local notification to do something while device is restarted, how? 
Create a seperate class file, and extend it from BroadcastReceiver. Then, you need the right attribute being set on top of the class. And, a special IntentFilter, see below.
This will be triggered when device restarted:
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] {"android.intent.action.BOOT_COMPLETED"})]
public class BootCompletedReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    { 
       //Do something here.  
       AlarmManagerHelper.SetAlarms();
    }
}



FULL EXAMPLE

Here is the example of how to create local notification in Xamarin.Android, starting by creating an creating an intent with couple of parameters; get an Alarm Manager, and set the future time of when you want to fire an the intent (a pending intent that will generate the local notification); plus the way of how to set the target page/intent of a click on the notification. 

More importantly, this via Broadcast Receiver, not WakefulBroafcast Receiver. 

Sample Code Of getting Alarm Manager.
MainActivity.cs
protected override void OnCreate(Bundle savedInstanceState)
{
     base.OnCreate(savedInstanceState);

     // Set our view from the "main" layout resource
     SetContentView(Resource.Layout.Main);

     // Get our button from the layout resource,
     // and attach an event to it
     Button button = FindViewById<Button>(Resource.Id.myButton);

     button.Click += delegate
     {
        //Create a Intent that going to fire notification. I call it 'AlarmReceiver' here. 
var alarmIntent = new Intent(this, typeof(AlarmReceiver));         DateTime now = DateTime.Now;         string message = "Jeff: " + now.Millisecond.ToString();

        // Pass param into Intent. Key and value.
        alarmIntent.PutExtra("title", "Hello");
        alarmIntent.PutExtra("message", message);

        // Create a pendingIntent, and perform broadcast. 
        // In Xamarin.Android, instead of passing context, use 'this'.
        var PI = PendingIntent.GetBroadcast(this, 0, alarmIntent, PendingIntentFlags.CancelCurrent);

        // Get the AlarmManager. Schedule time with 10 secs delay. It is in milliseconds.
        AlarmManager alarmManager = (AlarmManager)this.GetSystemService(Context.AlarmService);         alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 10 * 1000, PI);      }; }

Sample Code Of using BroadcastReceiver without wakeful and service.
AlarmReceiver.cs

//Specify the Broadcast Receiver attribute is the KEY thing in Xamarin project. 
//Note! This is extended from BroadcastReceiver. Everything is in Pending Intent. 
[BroadcastReceiver]
 public class AlarmReceiver : BroadcastReceiver
 {
        public AlarmReceiver()
        {
        }

        public override void OnReceive(Context context, Intent intent)
        {
            //Try to get the parameter set from previous intent (MainActivity) by key.
            var message = intent.GetStringExtra("message");
            var title = intent.GetStringExtra("title");

            //result Intent is the intent/page you going to show when user click on the local notification.
            //So, you can set the target page / callback page.
            var resultIntent = new Intent(context, typeof(SecondActivity));
            resultIntent.PutExtra("msg", "You clicked on the local notification!");

            //Specify how this intent is handled. 
            resultIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);
            //Again, need to put the page into a PendingIntent.
            var PI = PendingIntent.GetActivity(context, 0, resultIntent, PendingIntentFlags.CancelCurrent);
            //This is about how to use Notification.Builder to build a local notification.
            var builder = new Notification.Builder(context) .SetContentTitle(title) .SetContentText(message) .SetLargeIcon(BitmapFactory.DecodeResource(context.Resources, Resource.Drawable.icon))
                //Small Icon will always being used as the main icon in notification tray. 
                .SetSmallIcon(Resource.Drawable.icon) //Not quite sure how to use SetWhen
                //.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis() + 8000) 
                .SetPriority(1)
                //SetAutoCancel will auto remove the notification from system after a click on it.
                .SetAutoCancel(true) .SetDefaults(NotificationDefaults.All);             //Optional: Create Image notification.             Notification.BigPictureStyle picStyle = new Notification.BigPictureStyle();             picStyle.BigPicture(BitmapFactory.DecodeResource(context.Resources, Resource.Drawable.icon));             picStyle.SetSummaryText("This is image notification!! Hoho!");             builder.SetStyle(picStyle);             builder.SetContentIntent(PI);             var notification = builder.Build();
            //Get Notification Manager. Get ready to fire!
            var manager = NotificationManager.FromContext(context);

            //Having the same notification ID will always overwrite the old one.
            //If you prefer to keep each notification unique, then use a different unique / random id. 
            DateTime now = DateTime.Now;             var notificationID = now.Millisecond;             manager.Notify(notificationID, notification);         } }

Sample Code Of SecondActivity.
SecondActivity.cs
[Activity(Label = "SecondActivity")]
public class SecondActivity : Activity
{
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            //Get the value set in AlarmReceiver by key.
            string msg = Intent.GetStringExtra("msg");

            //If empty, then just return. 
            if (string.IsNullOrEmpty(msg))
            {
                return;
            }

            //Display the content.Get the Xml.
            SetContentView(Resource.Layout.Second);
            TextView textView = FindViewById<TextView>(Resource.Id.textView);
            textView.Text = string.Format("Param from Local notification: {0} .", msg);
        }
}

1 comment:

How to run unit test for your Xamarin Application in AppCenter?

How to run unit test for your Xamarin application in AppCenter?  When we talk about Building and Distributing your Xamarin app, you m...