2

I'm using the library https://github.com/aritchie/notifications and I can create and schedule notifications properly.

I wish to process them in Android so that depending on the notification - it will navigate to a particular page when the user taps on it.

I've found that the below event is fired when I tap on a notification (in my Android Project)

 protected override void OnNewIntent(Intent intent)
    {

    }

However, I can't find any info in the intent from my notification in order to build up navigation to a particular page.

Any advice would be appreciated.

Cheers!

Edit #1 (Adding additional code for a related issue):

If I fire off a notification, and close the app before the notification is received - I receive an error saying the app has crashed. If I receive the notification and close the app - I can load the app from the notification OK.

I have a dependency service which hits the following methods.

public void Remind(DateTime dateTime, string msgtype, string usermedid)
    {
        DateTime now = DateTime.Now;
        var diffinseconds = (dateTime - now).TotalSeconds;
        Intent alarmIntent = new Intent(Forms.Context, typeof(AlarmBroadcastReceiver));
        alarmIntent.PutExtra("notificationtype", msgtype);
        alarmIntent.PutExtra("id", id);


        PendingIntent pendingIntent = PendingIntent.GetBroadcast(Forms.Context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
        AlarmManager alarmManager = (AlarmManager)Forms.Context.GetSystemService(Context.AlarmService);

        //TODO: For demo set after 5 seconds.
        alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + diffinseconds * 1000, pendingIntent);

    }

    [BroadcastReceiver(Enabled = true)]
    [IntentFilter(new string[]{"android.intent.action.BOOT_COMPLETED"}, Priority = (int) IntentFilterPriority.LowPriority)]
    public class AlarmBroadcastReceiver : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            string notificationtype = intent.GetStringExtra("notificationtype");

            PowerManager.WakeLock sWakeLock;
            var pm = PowerManager.FromContext(context);
            sWakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "GCM Broadcast Reciever Tag");
            sWakeLock.Acquire();

            intent = new Intent(Forms.Context, typeof(MainActivity));
            intent.PutExtra("notificationtype", notificationtype);
            intent.AddFlags(ActivityFlags.IncludeStoppedPackages);



            // Instantiate the builder and set notification elements, including pending intent:
            NotificationCompat.Builder builder = new NotificationCompat.Builder(Forms.Context)
                .SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate)
                .SetAutoCancel(true)
                .SetContentIntent(PendingIntent.GetActivity(Forms.Context, 0, intent, 0)).SetContentTitle("Sample Notification")
                .SetContentText("Hello World! This is my first action notification!")
                .SetTicker("New Notification")

                .SetSmallIcon(Resource.Drawable.icon);


            // Build the notification:
            Android.App.Notification notification = builder.Build();

            notification.Flags = NotificationFlags.AutoCancel;

            // Get the notification manager:
            //NotificationManager notificationManager = Forms.Context.GetSystemService(Context.NotificationService) as NotificationManager;
            var manager = NotificationManagerCompat.From(context);

            // Publish the notification:
            const int notificationId = 0;
            manager.Notify(notificationId, notification);

            sWakeLock.Release();



        }
    }

How do I keep my Broadcast Receiver alive when the app is closed?

hvaughan3
  • 10,955
  • 5
  • 56
  • 76
Raymond Dillon
  • 197
  • 1
  • 18

1 Answers1

8

Ok so it took me some time to figure this one out. OnNewIntent is called when the app is in the background and the notification is clicked on. It is also called each time the app is minimized and the brought back up... so to tell the difference between the 2 events, you need to check the passed in Intent for what extra data is inside it. The extra data would have come from the Intent you made when you first initiated the notification.

Also make sure to set your MainActivity's LaunchMode to LaunchMode.SingleTop so that your app does not get restarted each time the notification is clicked on.

[Activity(LaunchMode = LaunchMode.SingleTop, ....)]
public class MainActivity : FormsApplicationActivity {

    ....

    /// <summary>
    /// Called when the app is in the background and a notification is clicked on (also called each time the app is minimized and the brought back up), a new <c>Intent</c> is created
    ///     and sent out, since we use <c>LaunchMode</c> set to <c>SingleTop</c> this method is called instead of the app being restarted.
    /// </summary>
    /// <param name="intent">The <c>Intent</c> that was set when the call was made. If started from a notification click, extra <c>string</c> values can be extracted.</param>
    protected override void OnNewIntent(Intent intent) {

        if(intent.HasExtra("Some special key you made up")) { //Here is where you check for special notification intent extras
            //Do something brilliant now that you know a notification was clicked on
        }
        base.OnNewIntent(intent);
    }

To see how you can add data to the Intent you can check out the Xamarin Sport App, but do not get too bogged down in all the other stuff they are doing like I always tend to do. Just focus on the PutExtra part.

Edit #1:

If your app is completely closed, you need to pull the data from the Intent passed into OnCreate and pass it into your App class or do something else with it:

protected override async void OnCreate(Android.OS.Bundle bundle) {

    base.OnCreate(bundle);

    Forms.Init(this, bundle);

    string parameterValue = Intent.GetStringExtra("Some special key you made up"); //This would come in from the Push Notification being clicked on

    Console.WriteLine("\nIn MainActivity.OnCreate() - Param Intent Extras: {0}\n", parameterValue);

    //MessagingCenter.Send("nothing", ConstantKeys.NewNotification); //Do something special with the notification data

    LoadApplication(parameterValue != null ? new App(parameterValue) : new App()); //Do something special with the notification data
}

Edit #2:

Some changes I would recommend to your OnReceive method based on my current code (some may not be necessary, but it is just what I am doing):

  • Label your Broadcast Receiver
  • Add stupid Xamarin constructors
  • Used constant property instead of string for IntentFilter
  • Remove IntentFilter Priority
  • Check for null Intent (might not be necessary)
  • Use Application.Context instead of Forms.Context (I use Forms.Context in other parts of my app so not sure about this one, but can't hurt)
  • Do not overwrite the passed in Intent
  • Create startup intent instead of regular
  • Add IncludeStoppedPackages flag before pulling out extras
  • Check for boot completed event
  • Use Notification.Builder instead of NotificationCompat.Builder (though you might need to change this back)
  • Add following flags to pendingintent: PendingIntentFlags.UpdateCurrent | PendingIntentFlags.OneShot -- Use NotificationManager (unless you have a specific reason you commented it out)
[assembly: UsesPermission(Android.Manifest.Permission.Vibrate)]  
[assembly: UsesPermission(Android.Manifest.Permission.WakeLock)]                //Optional, keeps the processor from sleeping when a message is received
[assembly: UsesPermission(Android.Manifest.Permission.ReceiveBootCompleted)]                        //Allows our app to be opened and to process notifications even when the app is closed

namespace Your.App.Namespace {

[BroadcastReceiver(Enabled = true, Label = "GCM Alarm Notifications Broadcast Receiver")]
[IntentFilter(new []{ Intent.ActionBootCompleted })]
public class AlarmBroadcastReceiver : BroadcastReceiver {
    #region Constructors

    // ReSharper disable UnusedMember.Global
    public AlarmBroadcastReceiver() { }

    public AlarmBroadcastReceiver(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { }
    // ReSharper restore UnusedMember.Global

    #endregion

    public void Remind(DateTime dateTime, string msgtype, string usermedid) {
        DateTime now = DateTime.Now;
        var diffinseconds = (dateTime - now).TotalSeconds;

        Intent alarmIntent = new Intent(Application.Context, typeof(AlarmBroadcastReceiver));
        alarmIntent.PutExtra("notificationtype", msgtype);
        alarmIntent.PutExtra("id", id);

        PendingIntent pendingIntent = PendingIntent.GetBroadcast(Application.Context, 0, alarmIntent, PendingIntentFlags.UpdateCurrent);
        AlarmManager alarmManager = (AlarmManager)Application.Context.GetSystemService(Context.AlarmService);

        //TODO: For demo set after 5 seconds.
        alarmManager.Set(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + diffinseconds * 1000, pendingIntent);
    }

    public override void OnReceive(Context context, Intent intent) {
        #region Null Check

        if(intent == null) {
            Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Intent is null\n");
            return;
        }

        #endregion

        intent.AddFlags(ActivityFlags.IncludeStoppedPackages);

        string action = intent.Action;
        Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Action: {0}\n", action);

        #region Boot Completed Check

        if(action.Equals("android.intent.action.BOOT_COMPLETED")) {

            PowerManager pm = PowerManager.FromContext(context);
            PowerManager.WakeLock sWakeLock = pm.NewWakeLock(WakeLockFlags.Partial, "GCM Broadcast Receiver Tag");
            sWakeLock.Acquire();

            Console.WriteLine("\nIn AlarmBroadcastReceiver.OnReceive() - Process Shared Preferences Notifications\n");

            #region Process Saved Scheduled Notifications

            //Get list of saved scheduled notifications that did not fire off before the device was turned off (I store them in SharedPreferences and delete them after they are fired off)

            //Go through the list and reschedule them

            #endregion

            sWakeLock.Release();
            return;
        }

        #endregion

        string notificationtype = intent.GetStringExtra("notificationtype");

        Intent startupIntent = Application.Context.PackageManager.GetLaunchIntentForPackage(Application.Context.PackageName);
        startupIntent.PutExtra("notificationtype", notificationtype);

        // Instantiate the builder and set notification elements, including pending intent:
        Notification.Builder builder = new Notification.Builder(Application.Context)
            .SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate)
            .SetAutoCancel(true)
            .SetContentIntent(PendingIntent.GetActivity(Application.Context, 0, intent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.OneShot))
            .SetContentTitle("Sample Notification")
            .SetContentText("Hello World! This is my first action notification!")
            .SetTicker("New Notification")
            .SetSmallIcon(Resource.Drawable.icon);

        // Build the notification:
        Android.App.Notification notification = builder.Build();

        // Get the notification manager:
        NotificationManager notificationManager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager;

        // Publish the notification:
        int notificationId = ??;//This should be a real unique number, otherwise it can cause problems if there are ever multiple scheduled notifications
        notificationManager.Notify(notificationId, notification);
    }
}
}
hvaughan3
  • 10,955
  • 5
  • 56
  • 76
  • My issue is that with the plugin that I'm using (https://github.com/aritchie/notifications) - I can't explicitly add content to the intent. – Raymond Dillon May 15 '17 at 08:11
  • @RaymondDillon I have not used that plugin but actually it looks like it is adding that explicit content from the `Notification.Metadata` property [here](https://github.com/aritchie/notifications/blob/master/Acr.Notifications.Android/NotificationsImpl.cs#L63). – hvaughan3 May 15 '17 at 14:03
  • My issue is keeping the notification alive after the app is closed. If the notification is received and the app is closed from the tray, OnNewIntent doesn't appear to fire and so my navigation logic doesn't work. I've tried using a broadcast receiver but to no avail. – Raymond Dillon May 16 '17 at 17:06
  • @RaymondDillon Correct. Like I put in the override comments for `OnNewIntent`, it only gets called if your app is in the background. See my edit to handle the app being completely closed. – hvaughan3 May 16 '17 at 17:57
  • You are a LEGEND - thank you! One last question - how do I keep my broadcast receiver alive when the app is closed (the app currently crashes if I close it before the scheduled notification is received) - I've added my current code as an edit. – Raymond Dillon May 18 '17 at 11:19
  • @RaymondDillon Happy to help. Added your edit to original question. In my code, I only request a wake lock within `OnReceive()` when my `BroadcastReceiver` gets the `BOOT_COMPLETED` alert which means the device was restarted completely. When I detect that event, my `BroadcastReceiver` is given the chance to reschedule all notifications that did not fire off yet (meaning I have to save those notifications until they fire and I must be able to access them using native Android code since PCL code will not be available when my `BroadcastReceiver` gets that event). See my edit for other stuff. – hvaughan3 May 18 '17 at 15:10
  • I'm still getting an error when I close the app before the notification is received (with no reboot) - using logcat the error is "Unable to start receiver" - Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' – Raymond Dillon May 19 '17 at 15:36
  • @RaymondDillon Lame!! See Edit #2 again, I changed your other method and I also added the permissions that I specify for my `AlarmService`. Does it give you a line number or anything remotely helpful? So you force close the app and immediately get the error or only after a schedule notification goes off? Perhaps add a bunch of `Console.WriteLine()`s everywhere to see where it gets and doesn't get. – hvaughan3 May 19 '17 at 17:19
  • I had made a mistake - my Application.Context call was ambiguous between Android.App.Context and Forms.Context. Have you had any luck in getting the LED to flash when a notification is received? I've tried setting permissions for it etc but had no luck. – Raymond Dillon May 23 '17 at 00:47
  • @RaymondDillon It is not something I ever tried doing, though it certainly looked confusing. – hvaughan3 May 23 '17 at 13:31