Push Notifications & Background Services

Requesting Notification Permissions

15 min Lesson 4 of 11

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_NOTIFICATIONS runtime permission is required, similar to camera or location. If not granted, notifications are blocked at the system level.
Note: On Android 13+ the 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;
  }
}
Tip: Do not request permission immediately on app launch. Show a custom rationale screen first — explain why your app needs notifications and what value they provide. Users who understand the reason are far more likely to grant permission. Only call 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.
Warning: Never spam the user with repeated permission prompts or misleading UI. Both Apple and Google can reject apps that use deceptive patterns to coerce notification opt-ins. Always respect the user's choice and make it easy to opt out later.

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.