-
-
Notifications
You must be signed in to change notification settings - Fork 72
5. Notification with Action
This guide explains how to add interactive actions to your local notifications, allowing users to respond directly from the notification. (Available from version 7.0.0 and above)
- Overview
- Setting Up Actions
- Implementing in .NET MAUI
- Android Inline Reply (Direct Reply)
- iOS Text Input Actions
- iOS Category Options
- Handling Action Responses
- Platform-Specific Considerations
Notification actions allow users to interact with your notifications without opening the app. Common use cases include:
- Accepting/Rejecting invitations
- Marking tasks as complete
- Quick replies / inline text input
- Snoozing reminders
Actions are grouped into categories. Each category can contain multiple actions and is identified by a NotificationCategoryType.
Each action has these key properties:
-
Id: Unique identifier for the action -
Title: Display text for the action button -
LaunchAppWhenTapped: Whether to open the app when action is tapped - Platform-specific settings for Android (
Android) and iOS/macOS (Apple)
using Plugin.LocalNotification;
using Plugin.LocalNotification.Core.Models;
using Plugin.LocalNotification.Core.Models.AndroidOption;
using Plugin.LocalNotification.Core.Models.AppleOption;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseLocalNotification(config =>
{
// Define a Status category with multiple actions
config.AddCategory(new NotificationCategory(NotificationCategoryType.Status)
{
ActionList = new HashSet<NotificationAction>
{
// Positive action that launches the app
new NotificationAction(100)
{
Title = "Accept",
Android =
{
LaunchAppWhenTapped = true,
IconName = { ResourceName = "accept_icon" }
},
Apple = { Action = AppleActionType.Foreground }
},
// Destructive action that doesn't launch the app
new NotificationAction(101)
{
Title = "Decline",
Android =
{
LaunchAppWhenTapped = false,
IconName = { ResourceName = "decline_icon" }
},
Apple = { Action = AppleActionType.Destructive }
}
}
});
});
return builder.Build();
}
}using Plugin.LocalNotification;
using Plugin.LocalNotification.Core.Models;
var notification = new NotificationRequest
{
NotificationId = 100,
Title = "Meeting Invitation",
Description = "Team meeting at 2 PM",
CategoryType = NotificationCategoryType.Status // This links the notification to the action category
};
await LocalNotificationCenter.Current.Show(notification);Android inline reply lets users type a response directly from the notification shade, without opening the app.
using Plugin.LocalNotification.Core.Models.AndroidOption;
var replyAction = new NotificationAction(200)
{
Title = "Reply",
Android =
{
LaunchAppWhenTapped = false,
Inputs =
[
new AndroidActionInput
{
Label = "Type your message…",
AllowFreeFormInput = true, // allow free-form typing (default)
// Choices = ["Yes", "No", "Later"] // optional quick-reply chips
}
]
}
};config.AddCategory(new NotificationCategory(NotificationCategoryType.Status)
{
ActionList = new HashSet<NotificationAction> { replyAction }
});When the user sends an inline reply, the typed text is available via e.Input:
private void OnNotificationActionTapped(NotificationActionEventArgs e)
{
if (e.ActionId == 200 && e.Input is not null)
{
// e.Input contains the text the user typed
Console.WriteLine($"User replied: {e.Input}");
}
}| Property | Type | Default | Description |
|---|---|---|---|
Label |
string |
"" |
Hint text shown inside the text field |
AllowFreeFormInput |
bool |
true |
Allow free-form typing in addition to choices |
Choices |
string[]? |
null |
Pre-defined quick-reply chips shown above the keyboard |
On iOS and macOS, you can create a text-input action (a UNTextInputNotificationAction) by setting TextInputButtonTitle on an AppleAction. This adds a text field to the notification and displays a send button.
var replyAction = new NotificationAction(200)
{
Title = "Reply",
Apple =
{
Action = AppleActionType.Background,
TextInputButtonTitle = "Send", // triggers UNTextInputNotificationAction
TextInputPlaceholder = "Type a message…" // optional placeholder
}
};The typed text is available in e.Input, the same property as Android:
private void OnNotificationActionTapped(NotificationActionEventArgs e)
{
if (e.ActionId == 200 && e.Input is not null)
{
Console.WriteLine($"User replied: {e.Input}");
}
}| Property | Type | Default | Description |
|---|---|---|---|
Action |
AppleActionType |
None |
Background / Foreground / Destructive |
Icon |
AppleActionIcon |
— | System or template icon (iOS 15+) |
TextInputButtonTitle |
string? |
null |
When set, creates a text-input action; this value is the send-button label |
TextInputPlaceholder |
string? |
null |
Placeholder text for the text field |
NotificationCategory.AppleOptions maps to UNNotificationCategoryOptions and controls how the category behaves on iOS and macOS.
using Plugin.LocalNotification.Core.Models.AppleOption;
config.AddCategory(new NotificationCategory(NotificationCategoryType.Status)
{
AppleOptions = AppleCategoryOptions.CustomDismissAction | AppleCategoryOptions.HiddenPreviewShowTitle,
ActionList = new HashSet<NotificationAction> { /* … */ }
});| Value | iOS Native | Description |
|---|---|---|
None |
— | No special options (default) |
CustomDismissAction |
CustomDismissAction |
Fire the dismiss action when the user explicitly dismisses the notification |
AllowInCarPlay |
AllowInCarPlay |
Allow this category in CarPlay environments |
HiddenPreviewShowTitle |
HiddenPreviewShowTitle |
Show the notification title even when previews are hidden |
HiddenPreviewShowSubtitle |
HiddenPreviewShowSubtitle |
Show the notification subtitle even when previews are hidden |
AllowAnnouncement |
AllowAnnouncement |
Allow Siri to announce the notification (iOS 13+) |
public partial class App : Application
{
public App()
{
InitializeComponent();
LocalNotificationCenter.Current.NotificationActionTapped += OnNotificationActionTapped;
MainPage = new MainPage();
}
private async void OnNotificationActionTapped(NotificationActionEventArgs e)
{
switch (e.ActionId)
{
case 100: // Accept action
await HandleAcceptAction(e.Request);
break;
case 101: // Decline action
await HandleDeclineAction(e.Request);
break;
case 200: // Inline reply (Android or iOS text input)
if (e.Input is not null)
await HandleReply(e.Request, e.Input);
break;
}
}
private async Task HandleAcceptAction(NotificationRequest request)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.DisplayAlert("Action Taken", $"Accepted: {request.Title}", "OK");
});
}
private async Task HandleDeclineAction(NotificationRequest request)
{
await LocalNotificationCenter.Current.Cancel(request.NotificationId);
}
private async Task HandleReply(NotificationRequest request, string userInput)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.DisplayAlert("Reply received", userInput, "OK");
});
}
}- Action icons must be added to the Android project's drawable resources.
- Use
AndroidActionInputonNotificationAction.Android.Inputsto enable inline reply. - The typed text is delivered in
NotificationActionEventArgs.Input. - On Android 7+ (API 24+), inline replies appear directly in the notification shade.
- Set
TextInputButtonTitleonNotificationAction.Appleto activate a text-input action (UNTextInputNotificationAction). - The typed text is delivered in
NotificationActionEventArgs.Input. - Use
NotificationCategory.AppleOptions(AppleCategoryOptionsflags) to control category-level behaviour such as dismiss actions and CarPlay support. - Action types:
-
Foreground: Opens the app -
Destructive: Red-coloured action for destructive operations -
Background: Handles action without opening the app
-
-
Keep action titles short and clear.
-
Use meaningful action IDs.
-
Handle all action responses, including
e.Inputfor reply actions. -
Consider platform-specific behaviours when testing.
-
Test actions in different app states (foreground / background / terminated).
.UseLocalNotification(config => { // Define a Status category with multiple actions config.AddCategory(new NotificationCategory(NotificationCategoryType.Status) { ActionList = new HashSet<NotificationAction> { // Positive action that launches the app new NotificationAction(100) { Title = "Accept", Android = { LaunchAppWhenTapped = true, IconName = { ResourceName = "accept_icon" } }, Apple = { Action = AppleActionType.Foreground } }, // Destructive action that doesn't launch the app new NotificationAction(101) { Title = "Decline", Android = { LaunchAppWhenTapped = false, IconName = { ResourceName = "decline_icon" } }, Apple = { Action = AppleActionType.Destructive } } } }); }); return builder.Build();} }
### 2. Create a Notification with Actions
```csharp
using Plugin.LocalNotification;
using Plugin.LocalNotification.Core.Models;
var notification = new NotificationRequest
{
NotificationId = 100,
Title = "Meeting Invitation",
Description = "Team meeting at 2 PM",
CategoryType = NotificationCategoryType.Status // This links the notification to the action category
};
await LocalNotificationCenter.Current.Show(notification);
LocalNotificationCenter.Current.RegisterCategoryList(new HashSet<NotificationCategory>
{
new NotificationCategory(NotificationCategoryType.Status)
{
ActionList = new HashSet<NotificationAction>
{
new NotificationAction(100)
{
Title = "Accept",
Apple = { Action = AppleActionType.Foreground }
},
new NotificationAction(101)
{
Title = "Decline",
Apple = { Action = AppleActionType.Destructive }
}
}
}
});public partial class App : Application
{
public App()
{
InitializeComponent();
LocalNotificationCenter.Current.NotificationActionTapped += OnNotificationActionTapped;
MainPage = new MainPage();
}
private async void OnNotificationActionTapped(NotificationActionEventArgs e)
{
switch (e.ActionId)
{
case 100: // Accept action
await HandleAcceptAction(e.Request);
break;
case 101: // Decline action
await HandleDeclineAction(e.Request);
break;
}
}
private async Task HandleAcceptAction(NotificationRequest request)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.DisplayAlert(
"Action Taken",
$"Accepted: {request.Title}",
"OK");
});
}
private async Task HandleDeclineAction(NotificationRequest request)
{
// Cancel the notification
await LocalNotificationCenter.Current.Cancel(request.NotificationId);
// Additional decline handling logic
}
}- Action icons must be added to the Android project's drawable resources
- Actions can be configured to launch the app or handle in the background
- Consider using appropriate icon resources for actions
- Supports different action types:
-
Foreground: Opens the app -
Destructive: Red-colored action for destructive operations -
Background: Handles action without opening app
-
- Actions appear in the expanded notification view
- Keep action titles short and clear
- Use meaningful action IDs
- Handle all action responses appropriately
- Consider platform-specific behaviors
- Test actions in different app states (foreground/background)

