Local notifications
Local notifications are alerts sent by apps installed on a device. Local notifications are often used for features such as:
- Calendar events
- Reminders
- Location-based triggers
Each platform requires its own native code implementation to send and receive local notifications. However, each platform implementation can be abstracted at the cross-platform layer so that there's a consistent API to send and receive local notifications in a .NET Multi-platform App UI (.NET MAUI) app.
Create a cross-platform abstraction
A .NET MAUI app should send and receive local notifications without concern for the underlying platform implementations. The following INotificationManagerService
interface is used to define a cross-platform API that can be used to interact with local notifications:
public interface INotificationManagerService
{
event EventHandler NotificationReceived;
void SendNotification(string title, string message, DateTime? notifyTime = null);
void ReceiveNotification(string title, string message);
}
This interface defines the following operations:
- The
NotificationReceived
event allows an app to handle incoming notifications. - The
SendNotification
method sends a notification at an optionalDateTime
. - The
ReceiveNotification
method processes a notification when received by the underlying platform.
The interface should be implemented on each platform that you want to support local notifications.
Implement local notifications on Android
On Android, a local notification is a message that's displayed outside your app's UI to provide reminders or other information from your app. Users can tap the notification to open your app, or can optionally take an action directly from the notification. For information about local notifications on Android, see Notifications overview on developer.android.com.
For a .NET MAUI app to send and receive notifications on Android, the app must provide an implementation of the INotificationManagerService
interface.
Send and receive local notifications
On Android, the NotificationManagerService
class implements the INotificationManagerService
interface, and contains the logic to send and receive local notifications:
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using AndroidX.Core.App;
namespace LocalNotificationsDemo.Platforms.Android;
public class NotificationManagerService : INotificationManagerService
{
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;
NotificationManagerCompat compatManager;
public event EventHandler NotificationReceived;
public static NotificationManagerService Instance { get; private set; }
public NotificationManagerService()
{
if (Instance == null)
{
CreateNotificationChannel();
compatManager = NotificationManagerCompat.From(Platform.AppContext);
Instance = this;
}
}
public void SendNotification(string title, string message, DateTime? notifyTime = null)
{
if (!channelInitialized)
{
CreateNotificationChannel();
}
if (notifyTime != null)
{
Intent intent = new Intent(Platform.AppContext, typeof(AlarmHandler));
intent.PutExtra(TitleKey, title);
intent.PutExtra(MessageKey, message);
intent.SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTop);
var pendingIntentFlags = (Build.VERSION.SdkInt >= BuildVersionCodes.S)
? PendingIntentFlags.CancelCurrent | PendingIntentFlags.Immutable
: PendingIntentFlags.CancelCurrent;
PendingIntent pendingIntent = PendingIntent.GetBroadcast(Platform.AppContext, pendingIntentId++, intent, pendingIntentFlags);
long triggerTime = GetNotifyTime(notifyTime.Value);
AlarmManager alarmManager = Platform.AppContext.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(Platform.AppContext, typeof(MainActivity));
intent.PutExtra(TitleKey, title);
intent.PutExtra(MessageKey, message);
intent.SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTop);
var pendingIntentFlags = (Build.VERSION.SdkInt >= BuildVersionCodes.S)
? PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable
: PendingIntentFlags.UpdateCurrent;
PendingIntent pendingIntent = PendingIntent.GetActivity(Platform.AppContext, pendingIntentId++, intent, pendingIntentFlags);
NotificationCompat.Builder builder = new NotificationCompat.Builder(Platform.AppContext, channelId)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetLargeIcon(BitmapFactory.DecodeResource(Platform.AppContext.Resources, Resource.Drawable.dotnet_logo))
.SetSmallIcon(Resource.Drawable.message_small);
Notification notification = builder.Build();
compatManager.Notify(messageId++, notification);
}
void CreateNotificationChannel()
{
// Create the notification channel, but only on API 26+.
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channelNameJava = new Java.Lang.String(channelName);
var channel = new NotificationChannel(channelId, channelNameJava, NotificationImportance.Default)
{
Description = channelDescription
};
// Register the channel
NotificationManager manager = (NotificationManager)Platform.AppContext.GetSystemService(Context.NotificationService);
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
}
}
The NotificationManagerService
class should be placed in your app's Platforms > Android folder. Alternatively, multi-targeting can be performed based on your own filename and folder criteria, rather than using the Platforms > Android folder. For more information, see Configure multi-targeting.
Android allows apps to define multiple channels for notifications. The NotificationManagerService
constructor creates a basic channel that's used to send notifications. The SendNotification
method defines the platform-specific logic required to create and send a notification. The ReceiveNotification
method is called by Android OS when a message is received, and invokes the NotificationReceived
event handler. For more information, see Create a notification on developer.android.com.
The SendNotification
method creates a local notification immediately, or at an exact DateTime
. A notification can be scheduled for an exact DateTime
using the AlarmManager
class, and the notification will be received by an object that derives from the BroadcastReceiver
class:
[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(NotificationManagerService.TitleKey);
string message = intent.GetStringExtra(NotificationManagerService.MessageKey);
NotificationManagerService manager = NotificationManagerService.Instance ?? new NotificationManagerService();
manager.Show(title, message);
}
}
}
Important
By default, notifications scheduled using the AlarmManager
class won't survive device restart. However, you can design your app to automatically reschedule notifications if the device is restarted. For more information, see Start an alarm when the device restarts in Schedule repeating alarms on developer.android.com. For information about background processing on Android, see Guide to background processing on developer.android.com.
Handle incoming notifications
The Android app must detect incoming notifications and notify the NotificationManagerService
instance. One way of achieving this is by modifying the MainActivity
class.
The Activity
attribute on the MainActivity
class should specify a LaunchMode
value of LaunchMode.SingleTop
:
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, //... )]
public class MainActivity : MauiAppCompatActivity
{
}
The SingleTop
mode prevents multiple instances of an Activity
from being started while the app is in the foreground. This LaunchMode
may not be appropriate for apps that launch multiple activities in more complex notification scenarios. For more information about LaunchMode
enumeration values, see Android Activity LaunchMode on developer.android.com.
The MainActivity
class also needs to be modified to receive incoming notifications:
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
CreateNotificationFromIntent(Intent);
}
protected override void OnNewIntent(Intent? intent)
{
base.OnNewIntent(intent);
CreateNotificationFromIntent(intent);
}
static void CreateNotificationFromIntent(Intent intent)
{
if (intent?.Extras != null)
{
string title = intent.GetStringExtra(LocalNotificationsDemo.Platforms.Android.NotificationManagerService.TitleKey);
string message = intent.GetStringExtra(LocalNotificationsDemo.Platforms.Android.NotificationManagerService.MessageKey);
var service = IPlatformApplication.Current.Services.GetService<INotificationManagerService>();
service.ReceiveNotification(title, message);
}
}
}
The CreateNotificationFromIntent
method extracts notification data from the intent
argument and passes it to the ReceiveNotification
method in the NotificationManagerService
class. The CreateNotificationFromIntent
method is called from both the OnCreate
method and the OnNewIntent
method:
- When the app is started by notification data, the
Intent
data will be passed to theOnCreate
method. - If the app is already in the foreground, the
Intent
data will be passed to theOnNewIntent
method.
Check for permission
Android 13 (API 33) and higher requires the POST_NOTIFICATIONS
permission for sending notifications from an app. This permission should be declared in your AndroidManifest.xml file:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
For more information about the POST_NOTIFICATIONS
permission, see Notification runtime permission on developer.android.com.
You should implement a permission class that checks for the permission declaration at runtime:
using Android;
namespace LocalNotificationsDemo.Platforms.Android;
public class NotificationPermission : Permissions.BasePlatformPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions
{
get
{
var result = new List<(string androidPermission, bool isRuntime)>();
if (OperatingSystem.IsAndroidVersionAtLeast(33))
result.Add((Manifest.Permission.PostNotifications, true));
return result.ToArray();
}
}
}
After the app has launched you should request the user to grant this permission before attempting to send a local notification:
PermissionStatus status = await Permissions.RequestAsync<NotificationPermission>();
For more information about .NET MAUI permissions, see Permissions.
Implement local notifications on iOS and Mac Catalyst
On Apple platforms, a local notification is a message that conveys important information to users. The system handles delivery of notifications based on a specified time or location. For information about local notifications on Apple platforms, see Scheduling a notification locally from your app on developer.apple.com.
For a .NET MAUI app to send and receive notifications on Apple platforms, the app must provide an implementation of the INotificationManagerService
interface.
Send and receive local notifications
On Apple platforms, the NotificationManagerService
class implements the INotificationManagerService
interface, and contains the logic to send and receive local notifications:
using Foundation;
using UserNotifications;
namespace LocalNotificationsDemo.Platforms.iOS;
public class NotificationManagerService : INotificationManagerService
{
int messageId = 0;
bool hasNotificationsPermission;
public event EventHandler? NotificationReceived;
public NotificationManagerService()
{
// Create a UNUserNotificationCenterDelegate to handle incoming messages.
UNUserNotificationCenter.Current.Delegate = new NotificationReceiver();
// Request permission to use local notifications.
UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
{
hasNotificationsPermission = approved;
});
}
public void SendNotification(string title, string message, DateTime? notifyTime = null)
{
// 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
};
}
}
The NotificationManagerService
class should be placed in your app's Platforms > iOS or Platforms > Mac Catalyst folder. Alternatively, multi-targeting can be performed based on your own filename and folder criteria, rather than using the Platforms folders. For more information, see Configure multi-targeting.
On Apple platforms, you must request permission to use notifications before attempting to schedule a notification. This occurs in the NotificationManagerService
constructor. For more information about local notification permission,, see Asking permission to use notifications on developer.apple.com.
The SendNotification
method defines the logic required to create and send a notification, and creates an immediate local notification using a UNTimeIntervalNotificationTrigger
object, or at an exact DateTime
using a UNCalendarNotificationTrigger
object. The ReceiveNotification
method is called by iOS when a message is received, and invokes the NotificationReceived
event handler.
Handle incoming notifications
On Apple platforms, to handle incoming messages you must create a delegate that subclasses UNUserNotificationCenterDelegate
:
using UserNotifications;
namespace LocalNotificationsDemo.Platforms.iOS;
public class NotificationReceiver : UNUserNotificationCenterDelegate
{
// Called if app is in the foreground.
public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
ProcessNotification(notification);
var presentationOptions = (OperatingSystem.IsIOSVersionAtLeast(14))
? UNNotificationPresentationOptions.Banner
: UNNotificationPresentationOptions.Alert;
completionHandler(presentationOptions);
}
// Called if app is in the background, or killed state.
public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
if (response.IsDefaultAction)
ProcessNotification(response.Notification);
completionHandler();
}
void ProcessNotification(UNNotification notification)
{
string title = notification.Request.Content.Title;
string message = notification.Request.Content.Body;
var service = IPlatformApplication.Current?.Services.GetService<INotificationManagerService>();
service?.ReceiveNotification(title, message);
}
}
The NotificationReceiver
class is registered as the UNUserNotificationCenter
delegate in the NotificationManagerService
constructor, and provides incoming notification data to the ReceiveNotification
method in the NotificationManagerService
class.
Implement local notifications on Windows
Local notifications in the Windows App SDK are messages that your app can send to your user while they are not currently inside your app. The notification content is displayed in a transient window in the bottom right corner of the screen and in the Notification Center. Local notifications can be used to inform the user of app status, or to prompt the user to take an action.
For information about local notifications on Windows, including implementation details for packaged and unpackaged apps, see App notifications overview.
Register platform implementations
Each NotificationManagerService
implementation should be registered against the INotificationManagerService
interface, so that the operations the interface exposes can be called from cross-platform code. This can be achieved by registering the types with the Services property of the MauiAppBuilder object in the CreateMauiApp
method in the MauiProgram
class:
#if ANDROID
builder.Services.AddTransient<INotificationManagerService, LocalNotificationsDemo.Platforms.Android.NotificationManagerService>();
#elif IOS
builder.Services.AddTransient<INotificationManagerService, LocalNotificationsDemo.Platforms.iOS.NotificationManagerService>();
#elif MACCATALYST
builder.Services.AddTransient<INotificationManagerService, LocalNotificationsDemo.Platforms.MacCatalyst.NotificationManagerService>();
#elif WINDOWS
builder.Services.AddTransient<INotificationManagerService, LocalNotificationsDemo.Platforms.Windows.NotificationManagerService>();
#endif
For more information about dependency injection in .NET MAUI, see Dependency injection.
Send and receive local notifications
A INotificationManagerService
implementation can be resolved through automatic dependency resolution, or through explicit dependency resolution. The following example shows using explicit dependency resolution to resolve a INotificationManagerService
implementation:
// Assume the app uses a single window.
INotificationManagerService notificationManager =
Application.Current?.Windows[0].Page?.Handler?.MauiContext?.Services.GetService<INotificationManagerService>();
For more information about resolving registered types, see Resolution.
Once the INotificationManagerService
implementation is resolved, its operations can be invoked:
// Send
notificationManager.SendNotification();
// Scheduled send
notificationManager.SendNotification("Notification title goes here", "Notification messages goes here.", DateTime.Now.AddSeconds(10));
// Receive
notificationManager.NotificationReceived += (sender, eventArgs) =>
{
var eventData = (NotificationEventArgs)eventArgs;
MainThread.BeginInvokeOnMainThread(() =>
{
// Take required action in the app once the notification has been received.
});
};
}
The NotificationReceived
event handler casts its event arguments to NotificationEventArgs
, which defines Title
and Message
properties:
public class NotificationEventArgs : EventArgs
{
public string Title { get; set; }
public string Message { get; set; }
}
On Android, when a notification is sent it appears as an icon in the status bar:
When you swipe down on the status bar the notification drawer opens:
Tapping on the notification launches the app. A notification remains visible in the notification drawer until it's dismissed by the app or user.
On iOS, incoming notifications are automatically received by an app without requiring user input:
On Mac Catalyst, incoming notifications are automatically received by Notification Centre:
For more information about Notification Centre, see Use Notification Centre on Mac on support.apple.com.