Lokale Benachrichtigungen in Xamarin.Forms

Beispiel herunterladen Das Beispiel herunterladen

Lokale Benachrichtigungen sind Warnungen, die von Anwendungen gesendet werden, die auf einem mobilen Gerät installiert sind. Lokale Benachrichtigungen werden häufig für Funktionen wie die folgenden verwendet:

  • Kalenderereignisse
  • Erinnerungen
  • Standortbasierte Auslöser

Jede Plattform behandelt die Erstellung, Anzeige und Verwendung lokaler Benachrichtigungen anders. In diesem Artikel wird erläutert, wie Sie eine plattformübergreifende Abstraktion erstellen, um lokale Benachrichtigungen mit Xamarin.Forms zu senden, zu planen und zu empfangen.

Anwendung für lokale Benachrichtigungen unter iOS und Android

Erstellen einer plattformübergreifenden Schnittstelle

Die Xamarin.Forms-Anwendung sollte unabhängig von den zugrunde liegenden Plattformimplementierungen Benachrichtigungen erstellen und nutzen. Die folgende INotificationManager-Schnittstelle ist in der freigegebenen Codebibliothek implementiert und definiert eine plattformübergreifende API, die von der Anwendung verwendet werden kann, um mit Benachrichtigungen zu interagieren:

public interface INotificationManager
{
    event EventHandler NotificationReceived;
    void Initialize();
    void SendNotification(string title, string message, DateTime? notifyTime = null);
    void ReceiveNotification(string title, string message);
}

Diese Schnittstelle wird in jedem Plattformprojekt implementiert. Das NotificationReceived-Ereignis ermöglicht der Anwendung, eingehende Benachrichtigungen zu verarbeiten. Die Initialize-Methode sollte eine beliebige native Plattformlogik ausführen, die zum Vorbereiten des Benachrichtigungssystems erforderlich ist. Die SendNotification-Methode sollte eine Benachrichtigung senden. Optional kann ein DateTime-Wert angegeben werden. Die ReceiveNotification-Methode sollte von der zugrunde liegenden Plattform aufgerufen werden, wenn eine Nachricht empfangen wird.

Verwenden der Schnittstelle in Xamarin.Forms

Nachdem eine Schnittstelle erstellt wurde, kann sie im freigegebenen Xamarin.Forms-Projekt verwendet werden, obwohl noch keine Plattformimplementierungen erstellt wurden. Die Beispielanwendung enthält eine ContentPage mit dem Namen MainPage.xaml mit folgendem Inhalt:

<StackLayout Margin="0,35,0,0"
             x:Name="stackLayout">
    <Label Text="Click the button below to create a local notification."
           TextColor="Red"
           HorizontalOptions="Center"
           VerticalOptions="Start" />
    <Button Text="Create Notification"
            HorizontalOptions="Center"
            VerticalOptions="Start"
            Clicked="OnSendClick" />
    <Label Text="Click the button below to schedule a local notification for in 10 seconds time."
           TextColor="Red"
           HorizontalOptions="Center"
           VerticalOptions="Start" />
    <Button Text="Create Notification"
            HorizontalOptions="Center"
            VerticalOptions="Start"
            Clicked="OnScheduleClick" />
</StackLayout>

Das Layout enthält Label-Elemente, die Anweisungen erläutern, und Button-Elemente, die bei Aktivierung eine Benachrichtigung senden oder planen.

Der MainPage-Klasse- CodeBehind verarbeitet das Senden und Empfangen von Benachrichtigungen:

public partial class MainPage : ContentPage
{
    INotificationManager notificationManager;
    int notificationNumber = 0;

    public MainPage()
    {
        InitializeComponent();

        notificationManager = DependencyService.Get<INotificationManager>();
        notificationManager.NotificationReceived += (sender, eventArgs) =>
        {
            var evtData = (NotificationEventArgs)eventArgs;
            ShowNotification(evtData.Title, evtData.Message);
        };
    }

    void OnSendClick(object sender, EventArgs e)
    {
        notificationNumber++;
        string title = $"Local Notification #{notificationNumber}";
        string message = $"You have now received {notificationNumber} notifications!";
        notificationManager.SendNotification(title, message);
    }

    void OnScheduleClick(object sender, EventArgs e)
    {
        notificationNumber++;
        string title = $"Local Notification #{notificationNumber}";
        string message = $"You have now received {notificationNumber} notifications!";
        notificationManager.SendNotification(title, message, DateTime.Now.AddSeconds(10));
    }

    void ShowNotification(string title, string message)
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            var msg = new Label()
            {
                Text = $"Notification Received:\nTitle: {title}\nMessage: {message}"
            };
            stackLayout.Children.Add(msg);
        });
    }
}

Der MainPage Klassenkonstruktor verwendet denXamarin.FormsDependencyService, um eine plattformspezifische instance von INotificationManagerabzurufen. Die Methoden OnSendClick und OnScheduleClicked verwenden die INotificationManager-Instanz, um neue Benachrichtigungen zu senden und zu planen. Die ShowNotification-Methode wird vom Ereignishandler aufgerufen, der an das NotificationReceived-Ereignis angefügt ist, und fügt ein neues Label in die Seite ein, wenn das Ereignis aufgerufen wird.

Der NotificationReceived-Ereignishandler wandelt seine Ereignisargumente in NotificationEventArgs um. Dieser Typ ist im freigegebenen Xamarin.Forms-Projekt definiert:

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

Weitere Informationen zu Xamarin.FormsDependencyServicefinden Sie unter Xamarin.Forms DependencyService.

Erstellen der Android-Schnittstellenimplementierung

Damit die Xamarin.Forms-Anwendung Benachrichtigungen unter Android senden und empfangen kann, muss die Anwendung eine Implementierung der INotificationManager-Schnittstelle bereitstellen.

Erstellen der AndroidNotificationManager-Klasse

Die AndroidNotificationManager-Klasse implementiert die INotificationManager-Schnittstelle:

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;

[assembly: Dependency(typeof(LocalNotifications.Droid.AndroidNotificationManager))]
namespace LocalNotifications.Droid
{
    public class AndroidNotificationManager : INotificationManager
    {
        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";

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

        NotificationManager manager;

        public event EventHandler NotificationReceived;

        public static AndroidNotificationManager Instance { get; private set; }

        public AndroidNotificationManager() => Initialize();

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

        public void SendNotification(string title, string message, DateTime? notifyTime = null)
        {
            if (!channelInitialized)
            {
                CreateNotificationChannel();
            }

            if (notifyTime != null)
            {
                Intent intent = new Intent(AndroidApp.Context, typeof(AlarmHandler));
                intent.PutExtra(TitleKey, title);
                intent.PutExtra(MessageKey, message);

                PendingIntent pendingIntent = PendingIntent.GetBroadcast(AndroidApp.Context, pendingIntentId++, intent, PendingIntentFlags.CancelCurrent);
                long triggerTime = GetNotifyTime(notifyTime.Value);
                AlarmManager alarmManager = AndroidApp.Context.GetSystemService(Context.AlarmService) as AlarmManager;
                alarmManager.Set(AlarmType.RtcWakeup, triggerTime, pendingIntent);
            }
            else
            {
                Show(title, message);
            }
        }

        public void ReceiveNotification(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.UpdateCurrent);

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

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

        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
        }
    }
}

Das assembly-Attribut oberhalb des Namespace registriert die INotificationManager-Schnittstellenimplementierung beim DependencyService.

Android ermöglicht Anwendungen, mehrere Kanäle für Benachrichtigungen zu definieren. Mit der Initialize-Methode wird ein einfacher Kanal erstellt, den die Beispielanwendung zum Senden von Benachrichtigungen verwendet. Die SendNotification-Methode definiert die plattformspezifische Logik, die erforderlich ist, um eine Benachrichtigung zu erstellen und zu senden. Die ReceiveNotification-Methode wird vom Android-Betriebssystem aufgerufen, wenn eine Nachricht empfangen wird, und ruft ihrerseits den Ereignishandler auf.

Die SendNotification-Methode erstellt sofort oder zu einem exakten Zeitpunkt (DateTime) eine lokale Benachrichtigung. Mithilfe der AlarmManager-Klasse kann eine Benachrichtigung für einen exakten Zeitpunkt (DateTime) geplant werden. Die Benachrichtigung wird durch ein Objekt empfangen, das von der BroadcastReceiver-Klasse abgeleitet wird:

[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(AndroidNotificationManager.TitleKey);
            string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);

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

Wichtig

Standardmäßig werden mit der AlarmManager-Klasse geplante Benachrichtigungen bei einem Geräteneustart nicht beibehalten. Sie können Ihre Anwendung jedoch so entwerfen, dass Benachrichtigungen automatisch neu geplant werden, wenn ein Gerät neu gestartet wird. Weitere Informationen finden Sie auf developer.android.com im Leitfaden Planen von wiederholten Alarmen im Abschnitt Starten eines Alarms bei Neustart eines Geräts und im Beispiel. Informationen zur Hintergrundverarbeitung unter Android finden Sie auf developer.android.com im Guide to background processing (Leitfaden zur Hintergrundverarbeitung).

Weitere Informationen zu Broadcast Receivers finden Sie unter Broadcast Receivers in Xamarin.Android.

Verarbeiten eingehender Benachrichtigungen unter Android

Die MainActivity-Klasse muss eingehende Benachrichtigungen erkennen und die AndroidNotificationManager-Instanz benachrichtigen. Das Activity-Attribut der MainActivity-Klasse sollte einen LaunchMode-Wert von LaunchMode.SingleTop angeben:

[Activity(
        //...
        LaunchMode = LaunchMode.SingleTop]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        // ...
    }

Der SingleTop-Modus verhindert, dass mehrere Instanzen einer Activity gestartet werden, während sich die Anwendung im Vordergrund befindet. Dieser LaunchMode eignet sich möglicherweise nicht für Anwendungen, die in komplexeren Benachrichtigungsszenarios mehrere Aktivitäten starten. Weitere Informationen zu LaunchMode-Enumerationswerten finden Sie unter Android Activity LaunchMode (Startmodus von Android-Aktivitäten).

In der MainActivity-Klasse wird Folgendes geändert, um eingehende Benachrichtigungen zu empfangen:

protected override void OnCreate(Bundle savedInstanceState)
{
    // ...

    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    LoadApplication(new App());
    CreateNotificationFromIntent(Intent);
}

protected override void OnNewIntent(Intent intent)
{
    CreateNotificationFromIntent(intent);
}

void CreateNotificationFromIntent(Intent intent)
{
    if (intent?.Extras != null)
    {
        string title = intent.GetStringExtra(AndroidNotificationManager.TitleKey);
        string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);
        DependencyService.Get<INotificationManager>().ReceiveNotification(title, message);
    }
}

Die CreateNotificationFromIntent-Methode extrahiert Benachrichtigungsdaten aus dem intent-Argument und stellt sie mithilfe der ReceiveNotification-Methode für den AndroidNotificationManager bereit. Die CreateNotificationFromIntent-Methode wird sowohl von der OnCreate-Methode als auch der OnNewIntent-Methode aufgerufen:

  • Wenn die Anwendung von Benachrichtigungsdaten gestartet wird, werden die Intent-Daten an die OnCreate-Methode übergeben.
  • Wenn die Anwendung sich bereits im Vordergrund befindet, werden die Intent-Daten an die OnNewIntent-Methode übergeben.

Android bietet viele erweiterte Optionen für Benachrichtigungen. Weitere Informationen finden Sie unter Benachrichtigungen in Xamarin.Android.

Erstellen der iOS-Schnittstellenimplementierung

Damit die Xamarin.Forms-Anwendung Benachrichtigungen unter iOS senden und empfangen kann, muss die Anwendung eine Implementierung des INotificationManager bereitstellen.

Erstellen der iOSNotificationManager-Klasse

Die iOSNotificationManager-Klasse implementiert die INotificationManager-Schnittstelle:

using System;
using Foundation;
using UserNotifications;
using Xamarin.Forms;

[assembly: Dependency(typeof(LocalNotifications.iOS.iOSNotificationManager))]
namespace LocalNotifications.iOS
{
    public class iOSNotificationManager : INotificationManager
    {
        int messageId = 0;
        bool hasNotificationsPermission;
        public event EventHandler NotificationReceived;

        public void Initialize()
        {
            // request the permission to use local notifications
            UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
            {
                hasNotificationsPermission = approved;
            });
        }

        public void SendNotification(string title, string message, DateTime? notifyTime = null)
        {
            // EARLY OUT: app doesn't have permissions
            if (!hasNotificationsPermission)
            {
                return;
            }

            messageId++;

            var content = new UNMutableNotificationContent()
            {
                Title = title,
                Subtitle = "",
                Body = message,
                Badge = 1
            };            

            UNNotificationTrigger trigger;
            if (notifyTime != null)
            {
                // Create a calendar-based trigger.
                trigger = UNCalendarNotificationTrigger.CreateTrigger(GetNSDateComponents(notifyTime.Value), false);
            }
            else
            {
                // Create a time-based trigger, interval is in seconds and must be greater than 0.
                trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(0.25, false);
            }                      

            var request = UNNotificationRequest.FromIdentifier(messageId.ToString(), content, trigger);
            UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
            {
                if (err != null)
                {
                    throw new Exception($"Failed to schedule notification: {err}");
                }
            });
        }

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

        NSDateComponents GetNSDateComponents(DateTime dateTime)
        {
            return new NSDateComponents
            {
                Month = dateTime.Month,
                Day = dateTime.Day,
                Year = dateTime.Year,
                Hour = dateTime.Hour,
                Minute = dateTime.Minute,
                Second = dateTime.Second
            };
        }
    }
}

Das assembly-Attribut oberhalb des Namespace registriert die INotificationManager-Schnittstellenimplementierung beim DependencyService.

Unter iOS müssen Sie die Berechtigung zum Verwenden von Benachrichtigungen anfordern, bevor Sie versuchen, eine Benachrichtigung zu planen. Die Initialize-Methode fordert die Autorisierung zur Verwendung lokaler Benachrichtigungen an. Die SendNotification-Methode definiert die erforderliche Logik, um eine Benachrichtigung zu erstellen und zu senden. Die ReceiveNotification-Methode wird von iOS aufgerufen, wenn eine Nachricht empfangen wird, und ruft ihrerseits den Ereignishandler auf.

Hinweis

Die SendNotification-Methode erstellt mithilfe eines UNTimeIntervalNotificationTrigger-Objekts sofort oder mithilfe eines UNCalendarNotificationTrigger-Objekts zu einem exakten Zeitpunkt (DateTime) eine lokale Benachrichtigung.

Verarbeiten eingehender Benachrichtigungen unter iOS

Unter iOS müssen Sie einen Delegaten erstellen, der die Unterklasse UNUserNotificationCenterDelegate bildet, um eingehende Nachrichten zu verarbeiten. Die Beispielanwendung definiert eine iOSNotificationReceiver-Klasse:

public class iOSNotificationReceiver : UNUserNotificationCenterDelegate
{
    public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
    {
        ProcessNotification(notification);
        completionHandler(UNNotificationPresentationOptions.Alert);
    }

    void ProcessNotification(UNNotification notification)
    {
        string title = notification.Request.Content.Title;
        string message = notification.Request.Content.Body;

        DependencyService.Get<INotificationManager>().ReceiveNotification(title, message);
    }    
}

Diese Klasse verwendet den DependencyService, um eine Instanz der iOSNotificationManager-Klasse zu erhalten, und stellt eingehende Benachrichtigungsdaten für die ReceiveNotification-Methode bereit.

Die AppDelegate-Klasse muss ein iOSNotificationReceiver-Objekt als UNUserNotificationCenter-Delegaten beim Starten der Anwendung angeben. Dies tritt in der FinishedLaunching-Methode auf:

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();

    UNUserNotificationCenter.Current.Delegate = new iOSNotificationReceiver();

    LoadApplication(new App());
    return base.FinishedLaunching(app, options);
}

iOS bietet viele erweiterte Optionen für Benachrichtigungen. Weitere Informationen finden Sie unter Benachrichtigungen in Xamarin.iOS.

Testen der Anwendung

Sobald die Plattformprojekte eine registrierte Implementierung der INotificationManager-Schnittstelle enthalten, kann die Anwendung auf beiden Plattformen getestet werden. Führen Sie die Anwendung aus, und klicken Sie auf eine der Schaltflächen für Benachrichtigung erstellen, um eine Benachrichtigung zu erstellen.

Unter Android werden Benachrichtigungen im Benachrichtigungsbereich angezeigt. Wenn auf die Benachrichtigung getippt wird, empfängt die Anwendung die Benachrichtigung und zeigt eine Meldung an:

Lokale Benachrichtigungen unter Android

Unter iOS werden eingehende Benachrichtigungen automatisch von der Anwendung empfangen, ohne dass eine Benutzereingabe erforderlich ist. Die Anwendung empfängt die Benachrichtigung und zeigt eine Meldung an:

Lokale Benachrichtigungen unter iOS