Xamarin FormsでAndroidの通知機能を作る Android S対応

Development

Android Sを対応しXamarin FormsでAndroidの通知機能を作る!

Xamarin Formsで通知機能を作成するにはAndroid側の機能を使う必要があります。

前提

Xamarin Formsのプロジェクトの概要についてはこちらを。

DependecyService

Dependency Serviceの使い方についてはこちらを。

Interface

まずAndroid側を呼び出すためのInterfaceを実装します。

using System;

namespace DearFutureMe.Interface
{
    public interface INotification
    {
        event EventHandler NotificationReceived;
        void Initialize();
        void Send(DateTime time, int id, string title, string message);

        void Clear();
        void Receive(string title, string message);
    }

    public class NotificationEventArgs : EventArgs
    {
        public string Title { get; set; }
        public string Message { get; set; }
    }
}

Android側のEntity

DependecyServiceの実体として受け取るAndroid側の実装です。
Android S以上ではPendingIntentFlags.MutableかPendingIntentFlags.ImmutableがPendingIntent作成時に必要になります。
設定していない場合、Exceptionが発生します。

Java.Lang.IllegalArgumentException
  Message=com.companyname.dearfutureme: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using AndroidX.Core.App;
using Xamarin.Forms;
using AndroidApp = Android.App.Application;
using DearFutureMe.Interface;
using DearFutureMe.Droid.Enties;
using DearFutureMe.Droid.Handlers;

[assembly: Dependency(typeof(Notifier))]
namespace DearFutureMe.Droid.Enties
{
    public class Notifier : INotification
    {
        const string channelId = "default";
        const string channelName = "Default";
        const string channelDescription = "The default channel for notifications.";

        public const string TitleKey = "title";
        public const string MessageKey = "message";
        public const string MessageId = "id";

        bool channelInitialized = false;
        int messageId = 0;
        int pendingIntentId = 0;

        NotificationManager manager;

        public event EventHandler NotificationReceived;

        public static Notifier Instance { get; private set; }

        public Notifier() => Initialize();
        public void Initialize()
        {
            if (Instance == null) {
                CreateNotificationChannel();
                Instance = this;
            }
        }

        public void Send(DateTime dateTime, int id, string title, string message)
        {
            if (!channelInitialized) {
                CreateNotificationChannel();
            }

            Intent intent = new Intent(AndroidApp.Context, typeof(AlarmHandler));
            intent.PutExtra(TitleKey, title);
            intent.PutExtra(MessageKey, message);
            intent.PutExtra(MessageId, id.ToString());

            PendingIntent pendingIntent = PendingIntent.GetBroadcast(AndroidApp.Context, id, intent, PendingIntentFlags.CancelCurrent | PendingIntentFlags.Mutable);
            System.Diagnostics.Debug.WriteLine($"notification set => {dateTime.ToString()}");
            long triggerTime = GetNotifyTime(dateTime.ToUniversalTime());
            AlarmManager alarmManager = AndroidApp.Context.GetSystemService(Context.AlarmService) as AlarmManager;
            alarmManager.Set(AlarmType.RtcWakeup, triggerTime, pendingIntent);
        }

        public void Receive(string title, string message)
        {
            var args = new NotificationEventArgs() {
                Title = title,
                Message = message,
            };
            NotificationReceived?.Invoke(null, args);
        }

        public void Show(string title, string message)
        {
            Intent intent = new Intent(AndroidApp.Context, typeof(MainActivity));
            intent.PutExtra(TitleKey, title);
            intent.PutExtra(MessageKey, message);

            PendingIntent pendingIntent = PendingIntent.GetActivity(AndroidApp.Context, pendingIntentId++, intent, PendingIntentFlags.Mutable);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, channelId)
                .SetContentIntent(pendingIntent)
                .SetContentTitle(title)
                .SetContentText(message)
                .SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.icon_feed))
                .SetSmallIcon(Resource.Drawable.icon_feed)
                .SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);

            Notification notification = builder.Build();
            manager.Notify(messageId++, notification);
        }

        public void Delete(int id)
        {
            Intent intent = new Intent(AndroidApp.Context, typeof(AlarmHandler));
            PendingIntent pendingIntent = PendingIntent.GetBroadcast(AndroidApp.Context, id, intent, PendingIntentFlags.Mutable);
            AlarmManager alarmManager = AndroidApp.Context.GetSystemService(Context.AlarmService) as AlarmManager;
            alarmManager.Cancel(pendingIntent);
        }

        public void Clear()
        {
            Delete(0);
            pendingIntentId = 0;
        }

        void CreateNotificationChannel()
        {
            manager = (NotificationManager)AndroidApp.Context.GetSystemService(AndroidApp.NotificationService);

            if (Build.VERSION.SdkInt >= BuildVersionCodes.O) {
                var channelNameJava = new Java.Lang.String(channelName);
                var channel = new NotificationChannel(channelId, channelNameJava, NotificationImportance.Default) {
                    Description = channelDescription
                };
                manager.CreateNotificationChannel(channel);
            }

            channelInitialized = true;
        }

        long GetNotifyTime(DateTime notifyTime)
        {
            DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(notifyTime);
            double epochDiff = (new DateTime(1970, 1, 1) - DateTime.MinValue).TotalSeconds;
            long utcAlarmTime = utcTime.AddSeconds(-epochDiff).Ticks / 10000;
            return utcAlarmTime; // milliseconds
        }
    }
}

 

BroadcastReceiverの実体

アプリが閉じている最中でもBroadcastReceiverを使うと指定した時間に起動してくれます。

using Android.Content;
using DearFutureMe.Droid.Enties;

namespace DearFutureMe.Droid.Handlers
{
    [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(Notifier.TitleKey);
                string message = intent.GetStringExtra(Notifier.MessageKey);

                Notifier manager = Notifier.Instance ?? new Notifier();
                manager.Show(title, message);
            }
        }
    }
}

 

呼び出し

AboutPage.xaml.csのOnAppearing()で5秒後に呼び出すように処理を呼び出します。

        protected override void OnAppearing()
        {
            base.OnAppearing();
            var notification = DependencyService.Get();
            notification.Send(DateTime.Now.AddSeconds(5), 100, "title", "notification!");

        }

実行結果

画面表示5秒後に通知が表示されるようになります。

コメント

タイトルとURLをコピーしました