Místní oznámení v Xamarin.Forms

Download Sample Stažení ukázky

Místní oznámení jsou upozornění odesílaná aplikacemi nainstalovanými na mobilním zařízení. Místní oznámení se často používají pro funkce, jako jsou:

  • Události kalendáře
  • Připomenutí
  • Triggery založené na poloze

Každá platforma zpracovává vytváření, zobrazování a spotřebu místních oznámení odlišně. Tento článek vysvětluje, jak vytvořit multiplatformní abstrakci pro odesílání, plánování a přijímání místních oznámení pomocí Xamarin.Forms.

Local notifications application on iOS and Android

Vytvoření rozhraní pro různé platformy

Aplikace Xamarin.Forms by měla vytvářet a využívat oznámení bez obav související implementace platformy. Následující INotificationManager rozhraní se implementuje v knihovně sdíleného kódu a definuje rozhraní API pro různé platformy, které může aplikace používat k interakci s oznámeními:

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

Toto rozhraní se implementuje v každém projektu platformy. Tato NotificationReceived událost umožňuje aplikaci zpracovávat příchozí oznámení. Metoda Initialize by měla provést jakoukoli logiku nativní platformy potřebnou k přípravě systému oznámení. Metoda SendNotification by měla odeslat oznámení na volitelném DateTimemístě . Metoda ReceiveNotification by měla být volána základní platformou při přijetí zprávy.

Využití rozhraní v Xamarin.Forms

Po vytvoření rozhraní je možné ho využívat ve sdíleném Xamarin.Forms projektu, i když implementace platformy ještě nebyly vytvořeny. Ukázková aplikace obsahuje ContentPage název MainPage.xaml s následujícím obsahem:

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

Rozložení obsahuje Label prvky, které vysvětlují pokyny, a Button prvky, které po klepnutí odesílají nebo plánují oznámení.

Kód MainPage třídy zpracovává odesílání a příjem oznámení:

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

Konstruktor MainPage třídy používá Xamarin.FormsDependencyService k načtení instance specifické pro platformu objektu INotificationManager. Tyto OnSendClick metody OnScheduleClicked používají INotificationManager instanci k odesílání a plánování nových oznámení. Metoda ShowNotification se volá z obslužné rutiny události připojené k NotificationReceived události a při vyvolání události vloží na stránku novou Label .

Obslužná rutina NotificationReceived události přetypuje argumenty události na NotificationEventArgs. Tento typ je definován ve sdíleném Xamarin.Forms projektu:

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

Další informace o službě Xamarin.FormsDependencyServiceDependencyService naleznete v tématuXamarin.Forms.

Vytvoření implementace rozhraní androidu

Xamarin.Forms Aby aplikace mohla odesílat a přijímat oznámení v Androidu, musí aplikace poskytovat implementaci INotificationManager rozhraní.

Vytvoření třídy AndroidNotificationManager

Třída AndroidNotificationManager implementuje INotificationManager rozhraní:

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

Atribut assembly nad oborem názvů registruje implementaci INotificationManager rozhraní pomocí DependencyService.

Android umožňuje aplikacím definovat více kanálů pro oznámení. Tato Initialize metoda vytvoří základní kanál, který ukázková aplikace používá k odesílání oznámení. Metoda SendNotification definuje logiku specifickou pro platformu potřebnou k vytvoření a odeslání oznámení. Metoda ReceiveNotification je volána operačním systémem Android při přijetí zprávy a vyvolá obslužnou rutinu události.

Metoda SendNotification vytvoří místní oznámení okamžitě nebo přesně DateTime. Oznámení může být naplánováno na přesné DateTime použití AlarmManager třídy a oznámení bude přijato objektem, který je odvozen od BroadcastReceiver třídy:

[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);
        }
    }
}

Důležité

Oznámení naplánovaná pomocí AlarmManager třídy ve výchozím nastavení nepřežijí restartování zařízení. Aplikaci ale můžete navrhnout tak, aby automaticky přeplánovala oznámení, pokud se zařízení restartuje. Další informace potřebujete, když se zařízení restartuje v plánovači opakujících se alarmů na developer.android.com a vzorek. Informace o zpracování na pozadí v Androidu najdete v průvodci zpracováním na pozadí na developer.android.com.

Další informace o přijímačích vysílání naleznete v tématu Přijímače vysílání v Xamarin.Android.

Zpracování příchozích oznámení v Androidu

Třída MainActivity musí detekovat příchozí oznámení a informovat AndroidNotificationManager instanci. Atribut Activity třídy MainActivity by měl obsahovat LaunchMode hodnotu LaunchMode.SingleTop:

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

Režim SingleTop zabraňuje spuštění více instancí Activity v době, kdy je aplikace v popředí. To LaunchMode nemusí být vhodné pro aplikace, které spouštějí více aktivit ve složitějších scénářích oznámení. Další informace o LaunchMode výčtových hodnotách naleznete v části Android Activity LaunchMode.

MainActivity Ve třídě je upravena tak, aby přijímala příchozí oznámení:

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

Metoda CreateNotificationFromIntent extrahuje data oznámení z argumentu intent a poskytne ji AndroidNotificationManager k použití ReceiveNotification metody. Metoda CreateNotificationFromIntent se volá z OnCreate metody i OnNewIntent metody:

  • Když aplikace spustí data oznámení, Intent data se předají metodě OnCreate .
  • Pokud je aplikace již v popředí, Intent data budou předána metodě OnNewIntent .

Android nabízí řadu rozšířených možností pro oznámení. Další informace najdete v tématu Oznámení v Xamarin.Android.

Vytvoření implementace rozhraní pro iOS

Xamarin.Forms Aby aplikace mohla odesílat a přijímat oznámení v iOSu, musí aplikace poskytnout implementaci INotificationManager.

Vytvoření třídy iOSNotificationManager

Třída iOSNotificationManager implementuje INotificationManager rozhraní:

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

Atribut assembly nad oborem názvů registruje implementaci INotificationManager rozhraní pomocí DependencyService.

V iOSu musíte před pokusem o naplánování oznámení požádat o oprávnění k používání oznámení. Metoda Initialize požaduje autorizaci pro použití místních oznámení. Metoda SendNotification definuje logiku potřebnou k vytvoření a odeslání oznámení. Metoda ReceiveNotification bude volána systémem iOS při přijetí zprávy a vyvolá obslužnou rutinu události.

Poznámka:

Metoda SendNotification vytvoří místní oznámení okamžitě pomocí objektu UNTimeIntervalNotificationTrigger nebo přesně DateTime pomocí objektu UNCalendarNotificationTrigger .

Zpracování příchozích oznámení v iOSu

V iOSu musíte vytvořit delegáta, který podtřídy UNUserNotificationCenterDelegate zpracovává příchozí zprávy. Ukázková aplikace definuje iOSNotificationReceiver třídu:

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

Tato třída používá DependencyService k získání instance iOSNotificationManager třídy a poskytuje příchozí data oznámení metodě ReceiveNotification .

Třída AppDelegate musí při spuštění aplikace zadat iOSNotificationReceiver objekt jako UNUserNotificationCenter delegáta. K tomu dochází v FinishedLaunching metodě:

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 nabízí mnoho pokročilých možností pro oznámení. Další informace najdete v tématu Oznámení v Xamarin.iOS.

Testování aplikace

Jakmile projekty platformy obsahují registrovanou implementaci INotificationManager rozhraní, můžete aplikaci otestovat na obou platformách. Spusťte aplikaci a kliknutím na tlačítko Vytvořit oznámení vytvořte oznámení.

V Androidu se oznámení zobrazí v oznamovací oblasti. Po klepnutí na oznámení aplikace obdrží oznámení a zobrazí zprávu:

Local notifications on Android

V iOSu se příchozí oznámení automaticky přijímají aplikací, aniž by vyžadovala uživatelský vstup. Aplikace obdrží oznámení a zobrazí zprávu:

Local notifications on iOS