Xamarin – Local notification for Android (1)

Create a local notification

How to build local notification for Android?

  • Create a notification channel by NotificationManager
  • Create an Activity intent
    • Add title, message,..
  • Create a PendingIntent for the above Activity intent
  • Create a NotificationCompat.Builder
  • Send notification by NotificationManager
// Create a notification channel by NotificationManager
private void CreateNotificationChannel()
{
    if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
    {
        NotificationImportance importance = NotificationImportance.High;

        NotificationChannel notificationChannel = new NotificationChannel(mChannelId, mChannelName, importance)
        {
            Description = mChannelDescription
        };
        notificationChannel.EnableLights(true);
        notificationChannel.EnableVibration(true);
        notificationChannel.SetShowBadge(true);
        notificationChannel.Importance = NotificationImportance.High;
        notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });

        if (mManager != null)
        {
            mManager.CreateNotificationChannel(notificationChannel);
        }
    }
}
public void Send(string title, string message)
{
    try
    {
        // Create an Activity intent
        Intent intent = new Intent(mContext, typeof(MainActivity));
        intent.PutExtra(mTitleKey, title);
        intent.PutExtra(mMessageKey, message);

        // Create a PendingIntent
        PendingIntent pendingIntent = PendingIntent.GetActivity(mContext, mPendingIntentId++, intent, PendingIntentFlags.UpdateCurrent);

        // Create a NotificationCompat.Builder
        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, mChannelId)
            .SetContentIntent(pendingIntent)
            .SetContentTitle(title)
            .SetContentText(message)
            .SetSmallIcon(Resource.Drawable.abc_btn_check_material)
            .SetVisibility((int)NotificationVisibility.Public)
            .SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);

        // Send notification
        var notification = builder.Build();
        mManager.Notify(mMessageId++, notification);
    }
    catch (Exception ex)
    {
        //
    }
}

How to build a schedule notification for Android?

  • Create a BroadcastReceiver sending a local notification
  • Create a BroadcastReceiver intent
    • Add title, message,..
  • Create a PendingIntent for the above BroadcastReceiver intent
  • Setup an AlarmManager to trigger this pending intent at a setting time
// Create a BroadcastReceiver sending a local notification
[BroadcastReceiver(Enabled = true, Label = "Local Notifications Broadcast Receiver")]
public class AlarmHandler : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        if (intent?.Extras != null)
        {
            string title = intent.GetStringExtra("title");
            string message = intent.GetStringExtra("message");
            AndroidNotificationManager.mInstance.Show(title, message);
        }
    }
}

...

public class AndroidNotificationManager {
    public void SendScheduleNotification(string title, string message, DateTime? notifyTime = null)
    {
        // Create a BroadcastReceiver intent
        Intent intent = new Intent(mContext, typeof(AlarmHandler));
        intent.PutExtra(mTitleKey, title);
        intent.PutExtra(mMessageKey, message);

        // Create a PendingIntent
        PendingIntent pendingIntent = PendingIntent.GetBroadcast(mContext, mPendingIntentId++, intent, PendingIntentFlags.CancelCurrent);

        // Setup an AlarmManager to trigger this pending intent
        long triggerTime = GetNotifyTime(notifyTime.Value);
        AlarmManager alarmManager = mContext.GetSystemService(Context.AlarmService) as AlarmManager;
        alarmManager.Set(AlarmType.RtcWakeup, triggerTime, pendingIntent);
    }
}

...

https://github.com/hung-nb/xamarin-localnotification

How to create daily schedule notification?

Set alarm every Wednesday

// LocalNotification { Title, Message }
// HourMinute { mHour, mMinute }
// dayOfWeek {1: Sunday, 2: Monday, 3: Tuesday, 4: Wednesday, 5: Thursday, 6: Friday, 7: Saturday}
// Example: Send notification at 9:00am every Wednesday: scheduleTime = { mHour: 9, mMinute: 0 }, dayOfWeek = 4
public void SetSchedule(LocalNotification notification, HourMinute scheduleTime, int dayOfWeek)
{
    var appContext = Application.Context;
    int id = 1234567890;

    // Create a BroadcastReceiver intent
    Intent intent = new Intent(appContext, typeof(AlarmBroadcastReceiver));
    intent.PutExtra("title", notification.Title);
    intent.PutExtra("message", notification.Message);

    // Create a PendingIntent
    PendingIntent pendingIntent = PendingIntent.GetBroadcast(appContext, id, intent, PendingIntentFlags.CancelCurrent);

    // Set trigger time
    var triggerTime = Calendar.Instance;
    while (triggerTime.Get(CalendarField.DayOfWeek) != dayOfWeek)
    {
        // if today is not dayOfWeek, then find next day until find that day
        // dayOfWeek = Calendar.Wednesday
        triggerTime.Add(CalendarField.Date, 1);
    }
    triggerTime.Set(CalendarField.HourOfDay, scheduleTime.mHour);
    triggerTime.Set(CalendarField.Minute, scheduleTime.mMinute);
    triggerTime.Set(CalendarField.Second, 0);

    Console.WriteLine("+++++++++++++" + triggerTime.Time);

    // Setup an AlarmManager to trigger this pending intent
    // Example: Send notification at 9:00am every Wednesday: scheduleTime = { mHour: 9, mMinute: 0 }, dayOfWeek = 4
    AlarmManager alarmManager = appContext.GetSystemService(Context.AlarmService) as AlarmManager;
    alarmManager.SetRepeating(AlarmType.RtcWakeup, triggerTime.TimeInMillis, AlarmManager.IntervalDay * 7, pendingIntent);
}

Set alarm from Monday to Friday

Set alarm repeating every day and in on Receive check current day. If its sat or sun ignore it

Create a background service using AlarmManager + BroadcastReceivers

(Deprecated) How to make notification work even when app is killed?

Call local notification (AlarmManager) in a foreground service

  • Create a service
    [Service]
    public class AlarmService : Service
    {
        IBinder binder;

        public override IBinder OnBind(Intent intent)
        {
            binder = new AlarmServiceBinder(this);
            return binder;
        }

        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
        {
            // this method would hold the code to be run when the service is started.
            // Call local notification here
            Send(message, title);

            return StartCommandResult.NotSticky;
        }
    }

    public class AlarmServiceBinder : Binder
    {
        readonly AlarmService service;

        public AlarmServiceBinder(AlarmService service)
        {
            this.service = service;
        }

        public AlarmService GetAlarmService()
        {
            return service;
        }
    }
  • Run service in StartForegroundService
        public void RunForegroundService()
        {
            var intent = new Intent(MainActivity.AppInstance, typeof(AlarmService));


            if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
            {
                MainActivity.AppInstance.StartForegroundService(intent);
            }
            else
            {
                MainActivity.AppInstance.StartService(intent);
            }

        }
  • Add this line into AndroidManifest
<application android:label="XXXXXX" android:usesCleartextTraffic="true">
    ...
    <receiver android:name=".AlarmBroadcastReceiver" android:enabled="true" android:exported="true" />
    ...
</application>

(New) How to make notification work even when app is killed?

Create a background service

using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Support.V4.App;
using static Android.OS.PowerManager;

namespace localnotification.Droid.Notification
{
    [Service(Enabled = true)]
    public class MyRequestService : Service
    {
        private Handler handler;
        private Action runnable;
        private bool isStarted;
        private WakeLock wakeLock;
        private long DELAY_BETWEEN_LOG_MESSAGES = 15000;
        private int NOTIFICATION_SERVICE_ID = 999999;
        private int NOTIFICATION_SERVICE_ALARM_ID = 9999990;
        private string NOTIFICATION_CHANNEL_ID = "888888";
        private string NOTIFICATION_CHANNEL_NAME = "channel_name_123";

        public override void OnCreate()
        {
            base.OnCreate();

            handler = new Handler();

            // Here is what you want to do always, i just want to push a notification every 15 seconds here
            runnable = new Action(() =>
            {
                DispatchNotificationThatAlarmIsGenerated("I'm running");
                handler.PostDelayed(runnable, DELAY_BETWEEN_LOG_MESSAGES);
            });
        }

        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
        {
            if (isStarted)
            {
                // service is already started
            }
            else
            {
                // Popup start service notification
                CreateNotificationChannel();
                DispatchNotificationThatServiceIsRunning();

                // Pop up notification every 15s
                handler.PostDelayed(runnable, DELAY_BETWEEN_LOG_MESSAGES);
                isStarted = true;

                // Keep app work when device sleeps
                PowerManager powerManager = (PowerManager)this.GetSystemService(Context.PowerService);
                WakeLock wakeLock = powerManager.NewWakeLock(WakeLockFlags.Full, "Client Lock");
                wakeLock.Acquire();
            }
            return StartCommandResult.Sticky;
        }

        public override void OnTaskRemoved(Intent rootIntent)
        {
            //base.OnTaskRemoved(rootIntent);
        }

        public override IBinder OnBind(Intent intent)
        {
            // Return null because this is a pure started service. A hybrid service would return a binder that would
            // allow access to the GetFormattedStamp() method.
            return null;
        }

        public override void OnDestroy()
        {
            // Stop the handler.
            handler.RemoveCallbacks(runnable);

            // Remove the notification from the status bar.
            var notificationManager = (NotificationManager)GetSystemService(NotificationService);
            notificationManager.Cancel(NOTIFICATION_SERVICE_ID);

            isStarted = false;
            wakeLock.Release();
            base.OnDestroy();
        }

        private void CreateNotificationChannel()
        {
            //Notification Channel
            NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationImportance.Max);
            notificationChannel.EnableLights(true);
            notificationChannel.LightColor = Color.Red;
            notificationChannel.EnableVibration(true);
            notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });

            NotificationManager notificationManager = (NotificationManager)this.GetSystemService(Context.NotificationService);
            notificationManager.CreateNotificationChannel(notificationChannel);
        }

        private void DispatchNotificationThatServiceIsRunning()
        {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                   .SetDefaults((int)NotificationDefaults.All)
                   .SetSmallIcon(Resource.Drawable.abc_btn_check_material)
                   .SetVibrate(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 })
                   .SetSound(null)
                   .SetChannelId(NOTIFICATION_CHANNEL_ID)
                   .SetPriority(NotificationCompat.PriorityDefault)
                   .SetAutoCancel(false)
                   .SetContentTitle("Mobile")
                   .SetContentText("My service started")
                   .SetOngoing(true);

            NotificationManagerCompat notificationManager = NotificationManagerCompat.From(this);

            //notificationManager.Notify(NOTIFICATION_SERVICE_ID, builder.Build());
            StartForeground(NOTIFICATION_SERVICE_ID, builder.Build());
        }

        private void DispatchNotificationThatAlarmIsGenerated(string message)
        {
            var intent = new Intent(this, typeof(MainActivity));
            intent.AddFlags(ActivityFlags.ClearTop);
            var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);

            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .SetSmallIcon(Resource.Drawable.abc_btn_check_material)
                .SetContentTitle("Alarm")
                .SetContentText(message)
                .SetAutoCancel(true)
                .SetContentIntent(pendingIntent);

            var notificationManager = (NotificationManager)GetSystemService(NotificationService);
            notificationManager.Notify(NOTIFICATION_SERVICE_ALARM_ID, notificationBuilder.Build());
        }
    }
}

Call service in MainActivity.cs

        public void StartMyRequestService()
        {
            var serviceToStart = new Intent(this, typeof(MyRequestService));
            StartService(serviceToStart);
        }

        public void StopMyRequestService()
        {
            var serviceToStart = new Intent(this, typeof(MyRequestService));
            StopService(serviceToStart);
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        protected override void OnPause()
        {
            base.OnPause();
            StartMyRequestService();
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            StartMyRequestService();
        }

        protected override void OnResume()
        {
            base.OnResume();
            StopMyRequestService();
        }

Add permission into Manifest file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.localnotification">
	<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
	<application android:label="localnotification.Android" android:theme="@style/MainTheme"></application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>

How to re-schedule notification after reboot?

Hold RECEIVE_BOOT_COMPLETED and have a BroadcastReceiver to receive the boot so that if the phone is rebooted you can re-schedule your alarms.

https://stackoverflow.com/questions/51196658/schedule-notification-at-monday-of-every-week-in-android

Create background service using WorkManager

WorkManager is a library that makes it easy to schedule deferrable, asynchronous tasks even if the app exits or the device restarts.

It was designed to be backwards compatible to API 14 and does so by wrapping JobScheduler, AlarmManager, and BroadcastReceivers all in one.

Using JobScheduler your app will be running on a device that is API 23+. Anything below, you’ll be using a combination of AlarmManager + BroadcastReceivers.

But the minimum interval for PeriodicWork is 15 minutes (same as JobScheduler Periodic Job).
If you need it every 30 seconds — consider using OneTimeWork and schedule it again when the work is done.
But
Approximately your location query every 30 seconds will responsible for about 10% battery drain.

Be the first to comment

Leave a Reply

Your email address will not be published.


*