Cross-platform local notification management for .NET MAUI apps using Shiny, supporting scheduled, repeating, and geofence-triggered notifications with channels, badges, and interactive actions.
Use this skill when the user needs to:
| Item | Value |
|---|---|
| NuGet Package | Shiny.Notifications |
| Primary Namespace | Shiny.Notifications |
| Registration Namespace | Shiny (extension methods on IServiceCollection) |
| iOS, Mac Catalyst, Android |
| Dependencies | Shiny.Core, Shiny.Locations, Shiny.Support.Repositories |
Register the notification services in your MauiProgram.cs:
using Shiny;
// Without a delegate (fire-and-forget notifications)
services.AddNotifications();
// With a delegate to handle notification taps
services.AddNotifications<MyNotificationDelegate>();
On iOS, you can optionally pass an IosConfiguration to control authorization and presentation options:
#if IOS || MACCATALYST
services.AddNotifications<MyNotificationDelegate>(new IosConfiguration(
UNAuthorizationOptions: UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound,
PresentationOptions: UNNotificationPresentationOptions.Banner | UNNotificationPresentationOptions.Badge | UNNotificationPresentationOptions.Sound
));
#endif
When generating code that uses Shiny Notifications, follow these conventions:
Always request access before sending notifications:
var access = await notificationManager.RequestAccess();
if (access != AccessState.Available)
{
// Handle denied permission
return;
}
Use AccessRequestFlags when the notification uses triggers:
AccessRequestFlags.TimeSensitivity for scheduled or repeating notifications.AccessRequestFlags.LocationAware for geofence-triggered notifications.RequestRequiredAccess extension method that infers flags from the notification object.A Notification must have a Message set -- validation will throw otherwise.
Only one trigger type per notification -- you cannot mix ScheduleDate, RepeatInterval, and Geofence on the same notification.
Implement INotificationDelegate for handling user taps:
public class MyNotificationDelegate : INotificationDelegate
{
public async Task OnEntry(NotificationResponse response)
{
// response.Notification -- the original notification
// response.ActionIdentifier -- which action button was pressed
// response.Text -- text reply if action was TextReply type
}
}
Create channels before sending notifications that reference them:
notificationManager.AddChannel(new Channel
{
Identifier = "alerts",
Importance = ChannelImportance.High,
Sound = ChannelSound.High
});
Use the convenience Send extension for simple notifications:
await notificationManager.Send("Title", "Message body");
For platform-specific properties, use the native subclasses:
AndroidNotification and AndroidChannelAppleNotification and AppleChannelAlways inject INotificationManager via constructor injection -- never create instances directly.
Use CancelScope wisely when cancelling:
CancelScope.DisplayedOnly -- clears only shown notifications.CancelScope.Pending -- clears only scheduled/triggered notifications.CancelScope.All -- clears everything (default).Notification: Both Shiny.Notifications and Shiny.Push define a Notification type. If both packages are referenced in the same project, do NOT add both namespaces as global usings. Use Shiny.Notifications.Notification FQN or a file-level using Shiny.Notifications; directive to disambiguate.AccessState result before attempting to send notifications.Channel.Default) always exists with Identifier = "Notifications" and ChannelImportance.Low.InvalidOperationException.BadgeCount only on immediate notifications (not triggered ones) -- validation will fail otherwise.IntervalTrigger with either Interval (raw TimeSpan) or TimeOfDay (daily/weekly recurring), never both.Center and Radius are both set on GeofenceTrigger.notification for the default small icon, or set SmallIconResourceName on AndroidNotification.RequestRequiredAccess extension method to automatically determine needed permission flags from a Notification object.Payload dictionary on Notification to pass custom data that you can read back in your INotificationDelegate.OnEntry.