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秒後に通知が表示されるようになります。
コメント