Platform Local Notifications
A Flutter facade for local notifications on Android, iOS, macOS, Linux, Windows, and web. Android includes messaging-style notifications with reply and mark-as-read actions. Other platforms display chat models as standard notifications.
Installation
dependencies:
platform_local_notifications: ^3.0.0
# Only needed when constructing chat avatars:
v_platform: ^2.2.0
Version 3.0 requires Dart 3.13 and Flutter 3.47 or newer.
It uses flutter_local_notifications: ^22.3.2, flutter_cache_manager: ^3.4.5,
and web: ^1.1.2. All direct package and example dependencies use their latest
stable versions. Flutter itself pins material_color_utilities and test_api;
these are kept at the SDK-compatible versions without dependency overrides.
The package re-exports the native notification plugin. Calls made directly to
that plugin now use named parameters; the PlatformNotifier facade remains
unchanged. See MIGRATION_GUIDE.md.
Standalone Material and Cupertino
The package itself uses Flutter foundation and native notification types without
requiring Material or Cupertino widgets. Consumers can use either of Flutter's
standalone design libraries. The example uses material_ui: ^1.8.0, and its
regression suite also exercises the notification facade under
cupertino_ui: ^1.1.4. No legacy package:flutter/material.dart or
package:flutter/cupertino.dart imports remain in maintained package/example code.
A compatibility bridge is only needed if your own widget subtree contains
unmigrated third-party widgets; the notification service does not need one.
Platform capabilities
| Platform | Standard notifications | Built-in chat actions | Backend |
|---|---|---|---|
| Android | Yes | Reply, mark as read | flutter_local_notifications |
| iOS | Yes | No; standard fallback | flutter_local_notifications |
| macOS | Yes | No; standard fallback | flutter_local_notifications |
| Linux | Yes | No; standard fallback | flutter_local_notifications |
| Windows | Yes | No; standard fallback | flutter_local_notifications |
| Web | Supported desktop browsers | No; standard fallback | Browser Notifications API |
Click events include the model's payload. OS settings can still suppress
notifications even when a permission request succeeds. Linux and Windows
have no application runtime permission prompt, so requestPermissions()
returns true there; this does not verify system notification settings.
Web notifications require HTTPS or localhost and permission requested from a user gesture. This package uses page-scoped notifications: keep the page open to receive clicks. Mobile browsers that require service-worker notifications, including iOS browsers, are not supported by this backend. Both JavaScript and WebAssembly compilation are supported; compilation does not establish browser permission or delivery support.
Setup
Android
Use Android API 24 or newer, compile SDK 36, Java 17, AGP 8.11.1 or newer, and core library desugaring as required by flutter_local_notifications 22.3.2. The example uses Gradle 9.3.1, AGP 9.1.0, and built-in Kotlin with Java 17.
Add this receiver inside your manifest's <application> for chat actions:
<receiver
android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver"
android:exported="false" />
Network avatars also require android.permission.INTERNET in the main
manifest. Provide an Android notification icon resource suitable for your
app; the default is @mipmap/ic_launcher.
iOS and macOS
For foreground notification presentation, set the notification center's delegate in your app delegate, as shown in the example's iOS runner:
UNUserNotificationCenter.current().delegate = self
The example adopts UIScene and Swift Package Manager on Apple platforms.
For additional native setup, custom categories, or background handlers, follow the underlying plugin's platform instructions. Built-in chat action buttons are Android-only. Critical-alert permission is not requested by the facade; applications with the required entitlement can use the exported platform plugin directly.
Linux and Windows
Linux requires a running desktop notification server. Windows uses the native
notification plugin with an application name, app user model ID, and GUID.
Defaults derive a stable identity from appName; production applications should
provide NotificationData.initializationSettingsWindows explicitly to keep
identity stable across application renames. The legacy local_notifier
dependency is removed, including its deprecated macOS notification APIs.
See the example for native runner configuration.
Usage
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:platform_local_notifications/platform_local_notifications.dart';
import 'package:v_platform/v_platform.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await PlatformNotifier.initialize(appName: 'My App');
// runApp(...);
}
Future<bool?> requestFromButton() => PlatformNotifier.requestPermissions();
Future<void> showMessage() async {
await PlatformNotifier.showChatNotification(
model: ChatNotificationModel(
id: 42,
title: 'New message',
body: 'Hello!',
userName: 'Alex',
userImageUrl: VPlatformFile.fromUrl(
networkUrl: 'https://example.com/avatar.jpg',
),
payload: 'conversation-42',
),
);
}
Future<void> showStandard() => PlatformNotifier.showNotification(
model: const NotificationModel(
id: 43,
title: 'Reminder',
body: 'Your meeting starts soon.',
payload: 'meeting-43',
),
);
StreamSubscription<BaseNotificationAction> listenToActions() {
return PlatformNotifier.actionStream.listen((action) {
switch (action) {
case NotificationClickAction():
// Navigate using action.payload.
break;
case NotificationReplyAction():
// Send action.replyText to the conversation in action.payload.
break;
case NotificationMarkReadAction():
// Mark the conversation in action.payload as read.
break;
}
});
}
Notification IDs must fit a signed 32-bit integer (-2147483648 through
2147483647). Reusing an ID replaces the notification on supporting
platforms. Do not use epoch milliseconds directly: those exceed the native
ID range. Empty titles and bodies remain allowed for custom native layouts.
model.isValid checks the identifier before delivery.
Avatars are optional (userImageUrl: null), loaded only for Android chat,
and cached for network images. Missing files and failed or slow downloads
fall back to text without blocking notification delivery indefinitely.
Configuration
const data = NotificationData(
androidNotificationChannel: AndroidNotificationChannel(
'messages',
'Messages',
description: 'Incoming messages',
importance: Importance.high,
),
initializationSettingsDarwin: DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
),
initializationSettingsLinux: LinuxInitializationSettings(
defaultActionName: 'Open',
),
);
// During application startup:
// await PlatformNotifier.initialize(appName: 'My App', notificationData: data);
NotificationData controls initialization. The older
NotificationConfiguration value type remains available for compatibility;
it is not accepted by initialize. The configured Android channel is used
for both standard and chat defaults. Individual models can provide
platform-specific notification details. Android chat generates its own
messaging style and action buttons, retaining channel, icon, sound,
vibration, tag, grouping, priority, importance, and color overrides.
Android channel settings are persisted by the OS; changing a channel's
importance or sound after creation requires a new channel ID or user action
in system settings.
Concurrent initialization calls share one operation. Once initialized, subsequent calls retain the first configuration. A failed initialization can be retried. Configure Apple permission prompts explicitly if you want to request permission later from a button.
Cancellation, launch, and lifecycle
Future<void> cancelMessage() => PlatformNotifier.cancelNotification(42);
Future<void> cancelEverything() => PlatformNotifier.cancelAllNotifications();
Future<bool> wasOpenedFromNotification() async {
final details = await PlatformNotifier.appLaunchNotification;
return details?.didNotificationLaunchApp == true;
}
Android tags can be passed to cancelNotification(id, tag: ...).
Web cancellation applies to notifications tracked during the current initialized
session. All native platforms use the delivery plugin for cancellation and
launch details. Web returns null for launch details;
Linux launch support depends on the notification server and runner setup.
Android chat actions dismiss the notification natively, including when the
main isolate is absent. Background actions are forwarded to actionStream
only while the main isolate and its receiver exist. The facade does not
persist or replay actions after app termination; applications needing that
behavior should configure their own durable background handling.
Cancel your StreamSubscription when its owner is disposed. Call
PlatformNotifier.dispose() when the application no longer needs the
service, rather than whenever an individual screen closes. Disposal
releases ports and backend listeners without dismissing existing
notifications. The singleton can be initialized again; disposal completes existing action
subscriptions, and a new action stream is available for the next lifecycle. If initialization is still pending, await it before
starting a new initialization after disposal.
Operations before initialization throw StateError; invalid model IDs throw
ArgumentError. Backend and permission errors propagate to the caller.
Notification payloads and reply text are not written to service debug logs.
Development
flutter pub get
dart format --output=none --set-exit-if-changed lib test example/lib example/test
flutter analyze
flutter test --coverage
cd example
flutter pub get
flutter test
flutter build web
flutter build web --wasm
flutter build apk --debug
For web development, run flutter run -d web-server --web-port 8765 in
example/, then open the localhost URL in a desktop browser.
Unit tests use mocked platform channels; builds and unit tests do not prove native permission prompts, delivery, or action behavior. Validate those on the affected OS before shipping. See CHANGELOG.md, MIGRATION_GUIDE.md, and LICENSE.