Fix iOS Action Buttons in Flutter Firebase Messaging by Aligning Categories and

Summary

Flutter apps using Firebase Cloud Messaging can display custom action buttons on iOS only when the notification payload, app capabilities, and notification categories are aligned. In the reported case the buttons never appear and the tap callbacks are not fired while everything works on Android and when the app is in background/terminated. The root cause is a mismatch between the iOS notification category name used in the payload and the one registered in DarwinNotificationCategory, combined with missing UNNotificationCategory registration during app launch.

Root Cause

  • Category identifier mismatch – the payload sent from Firebase uses a different category string than 'invitation_category' registered in the Flutter code. iOS discards the actions when identifiers differ.
  • Late registration – the notification categories are added after FlutterLocalNotificationsPlugin.initialize is called, so iOS never knows about them when the first notification arrives.
  • Missing UNUserNotificationCenter delegate – without assigning UNUserNotificationCenter.current().delegate = self (or the Flutter equivalent) iOS does not forward action taps to the app while it is in foreground.

Why This Happens in Real Systems

  • iOS requires exact category identifiers at both the server side (FCM payload) and client side (registered DarwinNotificationCategory).
  • The notification system is stateful: categories must be registered before any notification is delivered. In many projects the initialization order is tangled with other async startup work, causing silent failures.
  • Developers often test only on Android because action handling is more forgiving there, so the mis‑alignment goes unnoticed until iOS devices are used.

Real-World Impact

  • Users cannot accept or reject invitations directly from the lock screen, forcing them to open the app manually.
  • Business logic that depends on immediate user response (e.g., time‑sensitive approvals) fails, leading to lost revenue or operational delays.
  • Support tickets increase, as users report “buttons not showing” only on iOS devices, creating a platform‑specific bug churn.

Example or Code (if necessary and relevant)

// Register categories BEFORE initializing the plugin
final List iosCategories = [
  DarwinNotificationCategory(
    'invitation_category', // must match payload.category
    actions: [
      DarwinNotificationAction.plain(
        'accept_invitation',
        'Accept',
        options: {DarwinNotificationActionOption.foreground},
      ),
      DarwinNotificationAction.plain(
        'reject_invitation',
        'Reject',
        options: {DarwinNotificationActionOption.destructive},
      ),
    ],
    options: {DarwinNotificationCategoryOption.hiddenPreviewShowTitle},
  ),
];

final DarwinInitializationSettings iosSettings = DarwinInitializationSettings(
  requestAlertPermission: true,
  requestBadgePermission: true,
  requestSoundPermission: true,
  notificationCategories: iosCategories,
);

How Senior Engineers Fix It

  • Validate payload: Ensure the FCM JSON includes "category":"invitation_category" exactly as defined in code.
  • Early registration: Move the DarwinNotificationCategory setup above the call to _localNotifications.initialize.
  • Set delegate: In AppDelegate.swift (or via Flutter’s setForegroundNotificationPresentationOptions) assign the notification center delegate to handle action callbacks while the app is foreground.
  • Add defensive logging: After registration, log UNUserNotificationCenter.current().getNotificationCategories to confirm the category exists.
  • Write integration tests: Use Xcode UI tests to verify that a mock notification with the correct category renders buttons on the lock screen.

Why Juniors Miss It

  • They often treat the notification payload as opaque and assume the Flutter plugin will auto‑map actions.
  • They place initialization code inside asynchronous startup flows (e.g., after a network call), not realizing iOS needs the categories synchronously at launch.
  • Lack of familiarity with iOS notification internals (UNNotificationCategory, delegate) leads to missing the delegate assignment step.
  • Testing is frequently done only on Android or on iOS simulators where push notifications are stubbed, so the bug never surfaces during early development.

Leave a Comment