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);
        }
}

Apps built with iOS 10 and Xcode 8 can crash when the user accesses API's such as Camera, Calendar & Contacts

Apps built with iOS 10 and Xcode 8 can crash when the user accesses API's such as Camera, Calendar & Contacts. 


After some research, this is because iOS 10 and later required the following keys that have been introduced back in iOS 6!

My App crashed at this line: 
var accessGranted = DeviceHelper.Current.EventStore.RequestAccessAsync(EventKit.EKEntityType.Event) 


Resolved it by: 
Edit the info.plist:  Privacy - Calendars Usage Description
Date type = string.
Value = Please provide the reason why you app ask for the calendar usage.

Doc ref:
https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html

https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW15


Monday 19 September 2016

How to Schedule Notification in Android using Xamarin - Part3 (BOOT_COMPLETED and DontKillApp)

"Restart device will basically clear all the pending intent set previously. "

I found this sentence from one of the article in stack overflow. From there, I discovered the usage of 'BOOT_COMPLETED' and 'DontKillApp'. I am not 100% sure the original usage or even the definition to both of them. But, I think I can use it on my case after the result of few experiments.

Well, all my previous example of 'How to schedule notification in Android using Xamarin' were actually triggered by the button clicked event. Of course, you may argue I can actually move the logic to one of the override / start event of the app, so that it will fire or call the method when the app start.

"The keyword is 'app start'"...
Instead, what we want is when the device finished its booting process, not app start.
In many cases, we need to perform some task when device finished its batting process. For example: show notification that 'SIM has been changed', etc. For this kind of scenario, we need Broadcast receiver that should receiver "BOOT_COMPLETED" broadcast. We must know that when a device finishes booting android system sends 'BOOT_COMPLETED' broadcast.

Permission: ReceivedBootCompleted in Xamarin.
IntentFilter: [IntentFilter(new[] {"android.intent.action.BOOT_COMPLETED"})]

Filter to look for this intent only.


Example of using BOOT_COMPLETED
I created a class named 'BootReceiver' that extended from BroadcastReceiver.  As long as you have the right permission set, plus the correct attribute set in the class. The OnReceive() method will be called when the device finish the booting process. 
Sample source code:
[BroadcastReceiver (Enabled = true)]
    [IntentFilter(new[] {"android.intent.action.BOOT_COMPLETED"})]
    public class BootReceiver : BroadcastReceiver
    {
        
        public override void OnReceive(Context context, Intent intent)
        {
            var alarmIntent = new Intent(context, typeof(AlarmReceiver));
            var pending = PendingIntent.GetBroadcast(context, 0, alarmIntent, PendingIntentFlags.CancelCurrent);


            AlarmManager alertManager = (AlarmManager)context.GetSystemService(Context.AlarmService);
            Console.WriteLine("SystemClock.ElapsedRealtime() : " + SystemClock.ElapsedRealtime().ToString());
            alertManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 1 * 1000, pending);
        }
    }
 








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

You are recommended to read Part 1 if you haven't.
Part 1 - How to schedule Notification in Android using Xamarin - with Broadcast Receiver.

Part - 2:
Well, after the experiment of using Notification with Broadcast Receiver and Alarm Manager, I knew that my next steps is to make sure my notification should work in various scenarios, such as when my app is in the foreground, in background, or the mobile is in idle, and so on. Am I still able to receive the notification I have scheduled in all the different circumstances?


Concept:
1st of all, no different in MainActivity. You may copy the sample source code written in Part 1. Basically, we still need a Pending Intent that going to perform a Broadcast. We still need to set the pending intent into Alarm Manager. And, schedule it to a future time.

Secondly, instead of extending the AlarmReceiver with Broadcast Receiver, this time we use 'WakefulBroadcastReceiver'. This will wake the CPU so that message can be handled before going back to sleep.

As the WakefulBroadcastReceiver instance is just an extended BroadcastReceiver instance, we cannot execute long-running of asynchronous tasks. If we wish, we need to perform to start a task in a service using the static StartWakefulService() method.

Sample source code:
Here is the example of using WakefulBroadcast Receiver.
AlarmReceiver.cs - create a new intent - which is a service typed intent.

[BroadcastReceiver]
    public class AlarmReceiver : WakefulBroadcastReceiver
    {
        public AlarmReceiver()
        {
        }

        //Before we prevent the CPU from going to sleep, we need permission: Permission.WakeLock.
        //The recommended way to get hold of a wake lock is to use an instance of WakefulBroadcastReceive and invoke the StartWakefulService() method:
        public override void OnReceive(Context context, Intent intent)
        {
            var serviceIntent = new Intent(context, typeof(ListenerService));

            //Get the string value from the sender. 
            var title = intent.GetStringExtra("title");
            var message = intent.GetStringExtra("message");

            //Set them to service intent. 
            serviceIntent.PutExtra("title", title);
            serviceIntent.PutExtra("message", message);

            StartWakefulService(context, serviceIntent);
        }
    }
 



ListenerService.cs - service
[Service]
    public class ListenerService : IntentService
    {
        protected override void OnHandleIntent(Intent intent)
        {
            try
            {
                //perform the task
                Console.WriteLine("IN On HandleIntent: " + intent.Action);
                PublishNotification(this, intent);
            }
            finally
            {
                WakefulBroadcastReceiver.CompleteWakefulIntent(intent);
            }
        }

        private void PublishNotification(Context context, Intent intent)
        {
            var message = intent.GetStringExtra("message");
            var title = intent.GetStringExtra("title");

            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);

            var PI = PendingIntent.GetActivity(context, 0, resultIntent, PendingIntentFlags.CancelCurrent);

            var builder = new Notification.Builder(context)
                    .SetContentTitle(title)
                    .SetContentText(message)
                    .SetLargeIcon(BitmapFactory.DecodeResource(context.Resources, Resource.Drawable.icon))
                    .SetSmallIcon(Resource.Drawable.icon)
                    .SetPriority(1)
                    .SetAutoCancel(true)
                    .SetDefaults(NotificationDefaults.All);

            //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();

            var manager = NotificationManager.FromContext(context);

            DateTime now = DateTime.Now;
            var notificationID = now.Millisecond;

            manager.Notify(notificationID, notification);
        }
    }
  

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...