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




コメント