flutter_local_notifications icon indicating copy to clipboard operation
flutter_local_notifications copied to clipboard

[iOS] `FlutterLocalNotificationsPlugin.show` is not working when `setForegroundNotificationPresentationOptions(alert: false)` in FirebaseMessaging

Open choi88andys opened this issue 9 months ago • 1 comments

Describe

I am trying to show a heads-up push notification locally based on certain conditions. This works as expected on Android but not on iOS.

On iOS:

If I set alert: true in setForegroundNotificationPresentationOptions, the notification appears twice (one from the system and one from my local logic). If I set alert: false or omit setForegroundNotificationPresentationOptions, the push notification never appears.

I checked including #2474 and https://github.com/MaikuB/flutter_local_notifications/issues/2196. They contain relevant information but don’t fully resolve this issue. Please let me know if I missed something.

Question

@MaikuB Is this the intended behavior on iOS, or is it a bug? If this is expected, is creating a custom push alert the only way to show a heads-up push message?

To Reproduce

  1. Use Firebase Cloud Messaging (FCM) to receive push notifications.
  2. Set setForegroundNotificationPresentationOptions(alert: false, badge: true, sound: true).
  3. Listen to FirebaseMessaging.onMessage and show a local notification conditionally.
  4. Observe that no notification appears in the foreground.
  5. Set alert: true and observe that notifications appear twice.

Expected behavior

  • I expect to be able to show or hide the push notification conditionally when the app is in the foreground.
  • On iOS, setting alert: false completely prevents the notification from appearing, while setting alert: true always shows it.

Environment

  • Flutter version: 3.27.1
  • flutter_local_notifications version: 17.2.4, 19.0.0
  • firebase_messaging version: 15.1.3, 15.2.4
  • Device 1: iPhone 13 Pro, iOS 18.3.2
  • Device 2: iPhone 13, iOS 18.0

Sample code

// Before runApp
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await setupFlutterNotifications();
await FirebaseMessaging.instance.setAutoInitEnabled(true);
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
  await showFlutterNotification(message);
});
await configPushCondition();
// Configuration
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
late AndroidNotificationChannel channel;
bool isFlutterLocalNotificationsInitialized = false;

Future<void> setupFlutterNotifications() async {
  if (isFlutterLocalNotificationsInitialized) return;

  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: false,
    badge: false,
    sound: false,
  );

  channel = const AndroidNotificationChannel(
    'max_importance_channel',
    'max Importance Notifications',
    description: 'This channel is used for important notifications.',
    importance: Importance.max,
  );

  flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          IOSFlutterLocalNotificationsPlugin>()
      ?.requestPermissions(
        alert: true,
        badge: true,
        sound: true,
      );

  const initializationSettings = InitializationSettings(
    android: AndroidInitializationSettings('@mipmap/launcher_icon'),
    iOS: DarwinInitializationSettings(),
  );

  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse response) {
      _handleLocalNotificationTap(response.payload);
    },
  );

  isFlutterLocalNotificationsInitialized = true;
}

Future<void> showFlutterNotification(RemoteMessage message) async {
  final RemoteNotification? notification = message.notification;
  final AndroidNotification? android = message.notification?.android;

  if (notification != null && android == null) {
    await flutterLocalNotificationsPlugin.show(
      notification.hashCode,
      notification.title,
      notification.body,
      const NotificationDetails(
        iOS: DarwinNotificationDetails(
          presentAlert: true,
          presentBadge: true,
          presentBanner: true,
          presentSound: true,
          presentList: true,
          interruptionLevel: InterruptionLevel.timeSensitive,
        ),
      ),
    );
  }
}

Future<void> configPushCondition() async {
  final fcm = FirebaseMessaging.instance;
  await fcm.requestPermission();

  if (Platform.isIOS) {
    // return when Simulator

    var apnsToken = await fcm.getAPNSToken();
    if (apnsToken == null) {
      await Future<void>.delayed(const Duration(seconds: 1));
      apnsToken = await fcm.getAPNSToken();
      if (apnsToken == null) {
        print(
          'Can not subscribe topic on this launch by getAPNSToken failed',
        );
        return;
      }
    }
  }

  print('fcmToken: ${await fcm.getToken()}');
}

@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {}


choi88andys avatar Mar 18 '25 02:03 choi88andys

Sorry for the late response. Since you mentioned FCM being used, I believe there's a conflict and issued caused by the FCM plugin. Have you raised this issue on their repository? This plugin tries to make sure that it only handles local notifications created from the plugin. There was period where I contributed a fix to the FCM plugin when Google managed it as it was causing clashes. At the time, the plugin didn't check to some info within the notification to ensure it only processes push notifications from FCM. The PR I submitted was merged but then I saw Invertase took over and I suspect that code has been removed

MaikuB avatar Jun 20 '25 12:06 MaikuB