Requesting Notification Permissions
Requesting Notification Permissions
Before your Flutter app can display push notifications, it must explicitly ask the user for permission. On iOS, this has always been required. Starting with Android 13 (API level 33), Google introduced a runtime POST_NOTIFICATIONS permission, making explicit permission requests mandatory on modern Android devices as well. Using the firebase_messaging package, you can handle the full grant/deny/provisional flow in a single, unified API.
Why Permissions Matter on Each Platform
The two major mobile platforms handle notification permissions in fundamentally different ways:
- iOS: Notifications are always opt-in. The OS displays a native prompt the first time you call
requestPermission(). The user can grant full permission, deny it, or (on iOS 12+) allow provisional delivery — notifications appear silently in the Notification Centre without banners or sounds until the user explicitly acts on them. - Android < 13: Notifications were opt-out. Apps could send them without asking. No runtime prompt existed.
- Android 13+: A new
POST_NOTIFICATIONSruntime permission is required, similar to camera or location. If not granted, notifications are blocked at the system level.
firebase_messaging package automatically adds the POST_NOTIFICATIONS permission to your AndroidManifest.xml. You still need to call requestPermission() in Dart to trigger the runtime dialog.The AuthorizationStatus Enum
After requesting or checking permission, firebase_messaging returns a NotificationSettings object. Its authorizationStatus field is one of four values:
AuthorizationStatus.authorized— the user has fully granted permission; banners, sounds, and badges are all active.AuthorizationStatus.denied— the user explicitly denied permission. You cannot show the system prompt again; you should guide the user to Settings.AuthorizationStatus.provisional— iOS 12+ only; silent delivery to Notification Centre is allowed but no intrusive alerts.AuthorizationStatus.notDetermined— the user has never been asked yet (iOS) or the status is unknown.
Requesting Permission at Runtime
Call FirebaseMessaging.instance.requestPermission() to trigger the platform dialog. You can customise which iOS alert types to request by passing named parameters. The method is a no-op on Android < 13 but still returns the current status, so your code path stays unified.
Basic Permission Request
import 'package:firebase_messaging/firebase_messaging.dart';
Future<void> requestNotificationPermission() async {
final FirebaseMessaging messaging = FirebaseMessaging.instance;
final NotificationSettings settings = await messaging.requestPermission(
alert: true, // Show banner alerts
announcement: false,
badge: true, // Update app icon badge count
carPlay: false,
criticalAlert: false,
provisional: false, // Set true to request provisional on iOS
sound: true, // Play notification sounds
);
switch (settings.authorizationStatus) {
case AuthorizationStatus.authorized:
print('User granted full notification permission.');
break;
case AuthorizationStatus.provisional:
print('User granted provisional permission (iOS silent delivery).');
break;
case AuthorizationStatus.denied:
print('User denied notification permission.');
break;
case AuthorizationStatus.notDetermined:
print('Permission status has not been determined yet.');
break;
}
}
requestPermission() after the user acknowledges your rationale.Checking Existing Permission Status Without Prompting
Sometimes you need to check the current permission status without re-triggering the system dialog — for example, to decide whether to show a "Re-enable Notifications" banner. Use getNotificationSettings() for a non-intrusive read of the current status.
Checking Permission Status Silently
Future<AuthorizationStatus> checkNotificationPermissionStatus() async {
final NotificationSettings settings =
await FirebaseMessaging.instance.getNotificationSettings();
final AuthorizationStatus status = settings.authorizationStatus;
if (status == AuthorizationStatus.authorized) {
// Safe to fetch FCM token and subscribe to topics
final String? token = await FirebaseMessaging.instance.getToken();
print('FCM Token: $token');
} else if (status == AuthorizationStatus.denied) {
// Guide user to re-enable in device Settings
print('Notifications are disabled. Prompt user to open Settings.');
}
return status;
}
Handling the Denied State: Deep-Link to Settings
Once a user has denied permission, the system dialog cannot be shown again. Your only option is to direct the user to the device's notification settings. Use the app_settings package (or permission_handler) to open the correct settings screen directly.
- Show a clear in-app explanation of what they will miss without notifications.
- Provide a button labelled "Open Settings" that deep-links to the app's notification settings.
- After returning from Settings, call
getNotificationSettings()again to refresh the status.
Requesting Provisional Permission on iOS
Provisional authorisation is an iOS-only feature that lets notifications arrive silently in the Notification Centre without ever showing a system prompt. This lowers friction for first-time users: they see real notification content before deciding whether to "Keep" or "Turn Off" notifications. To request it, set provisional: true:
Provisional Permission (iOS Only)
Future<void> requestProvisionalPermission() async {
final NotificationSettings settings =
await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: true, // iOS 12+ only; silently ignored on Android
);
if (settings.authorizationStatus == AuthorizationStatus.provisional) {
// Deliver quiet notifications; iOS will ask the user to
// promote to full permission when they interact with one.
print('Provisional permission granted on iOS.');
}
}
Summary
Correctly managing notification permissions is the foundation of a reliable push notification system. Use requestPermission() to trigger the platform dialog (required on iOS always, on Android 13+), inspect AuthorizationStatus to branch your logic, and use getNotificationSettings() to read status silently without prompting. Always respect the user's decision: guide denied users to Settings, and consider provisional permission on iOS to build trust before asking for full opt-in.