firebase / flutterfire

🔥 A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.64k stars 3.96k forks source link

[firebase_messaging]: FCM Notifications Displayed When App is in Foreground on iOS (Shouldn't Be) #13271

Closed rrakesh28 closed 2 weeks ago

rrakesh28 commented 2 weeks ago

Is there an existing issue for this?

Which plugins are affected?

Messaging

Which platforms are affected?

iOS

Description

Hello,

I am encountering an issue where Firebase Cloud Messaging (FCM) notifications are being displayed even when my iOS app is in the foreground. This behavior is not intended; notifications should only be displayed when the app is in the background or terminated.

The issue is specific to iOS, where notifications are shown as alerts even when the app is active. This is in contrast to the desired behavior, where notifications should be handled programmatically or not displayed when the app is in the foreground.

Steps to Reproduce:

Send an FCM notification while the iOS app is in the foreground. Observe that the notification is displayed as an alert.

Expected Behavior:

Notifications should not be displayed as alerts when the app is in the foreground. Instead, they should be handled in the app or suppressed from being shown.

Actual Behavior:

Notifications are displayed as alerts when the app is in the foreground, even though the intention is to handle them manually or suppress them.

Reproducing the issue

fcm payload

final Map<String, dynamic> payload = {
      "message": {
        "token": fcmToken,
        "data": {
          "type": notificationTypeString,
          "notification": notification,
          'display_notification': 'false',
        },
        if (title != null || subTitle != null)
          "notification": {
            "title": title,
            "body": subTitle,
          },
      },
    };

import 'dart:async'; @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); print("Handling a background message: ${message.messageId}"); print(message.data.toString()); print(message.data['type']); print(message.data['notification']); }

final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

@override void initState() { super.initState(); _fetchInitialData(); _initializeFirebaseMessaging(); }

Future _initializeFirebaseMessaging() async { await _firebaseMessaging.requestPermission(); try { await _firebaseMessaging.subscribeToTopic('test'); print('subscription suceess'); } catch (e) { print('subscription error'); print(e); }

FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
  print("Foreground message: ${message.notification?.body.toString()}");
  print(message.data.toString());
  print(message.data['type']);
  print(message.data['notification']);
  print(message.data['display_notification']);
  bool shouldShowNotification =
      message.data['display_notification'] == 'true' ? true : false;
});

FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

}

Future _fetchInitialData() async { await _getToken(); }

Future _getToken() async { String? fcmToken = await _firebaseMessaging.getToken();

}

Firebase Core version

3.2.0

Flutter Version

3.24.0

Relevant Log Output

No response

Flutter dependencies

Expand Flutter dependencies snippet
```yaml dependencies: flutter: sdk: flutter flutter_bloc: ^8.1.4 get_it: ^7.6.7 firebase_core: ^3.2.0 firebase_auth: ^5.1.0 cloud_firestore: ^5.0.0 fluttertoast: ^8.2.4 hive: ^2.2.3 path_provider: ^2.1.2 quick_notify: ^0.2.1 image_picker: ^1.1.1 uuid: ^4.4.0 envied: ^0.5.4+1 universal_io: ^2.2.2 crypto: ^3.0.3 http: ^0.13.6 file_picker: ^8.0.3 converter: ^0.4.0 xml: ^6.5.0 s3_storage: ^1.0.4 bubble: ^1.2.1 amazon_cognito_upload: ^0.0.5 path: ^1.9.0 intl: ^0.18.1 dio: ^5.4.3+1 url_launcher: ^6.2.6 emoji_picker_flutter: ^2.2.0 open_file: ^3.3.2 open_app_file: ^4.0.2 connectivity_plus: ^6.0.3 awesome_notifications: ^0.8.2 get: ^4.6.6 windows_notification: ^1.2.0 fast_cached_network_image: ^1.2.9 shared_preferences: ^2.2.3 accordion: ^2.6.0 supabase_flutter: ^2.5.6 cached_network_image: ^3.3.1 firebase_messaging: 15.0.3 googleapis: ^13.1.0 googleapis_auth: ^1.4.1 flutter_sound: ^9.6.0 photo_view: ^0.15.0 just_audio: ^0.9.39 in_app_update: ^4.2.3 flutter_launcher_icons: ^0.13.1 audio_session: ^0.1.21 audioplayers: ^6.0.0 permission_handler: ^11.3.1 firebase_database: ^11.0.0 geolocator: ^12.0.0 dart_ipify: ^1.1.1 geocoding: ^3.0.0 hive_ui: ^1.0.14 timezone: ^0.9.4 package_info_plus: ^8.0.2 ```

Additional context and comments

No response

SelaseKay commented 2 weeks ago

Hi @rrakesh28 , this behaviour is indeed not intended. But the default behaviour can be overridden. Can you check if you're accidentally overriding the default behaviour in code with something like this:

await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true,
    badge: true,
    sound: true,
  );
rrakesh28 commented 2 weeks ago

No, im not overriding the default behaviour. It is working as expected in android, but not in ios. Is there is any configuration required in app delegate?

import UIKit
import Flutter
import FirebaseCore

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    FirebaseApp.configure()
application.registerForRemoteNotifications()
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  override func applicationDidBecomeActive(_ application: UIApplication) {
    application.applicationIconBadgeNumber = 0;
  }

}
SelaseKay commented 2 weeks ago

I don't think you'll have add anything to your app delegate. Can you provide a full sample code reproducing this issue? You can set up a minimal project and share the github link here.

rrakesh28 commented 2 weeks ago

main.dart

import 'package:flutter/material.dart';
import 'package:test_notification/test_page.dart';

import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Test Notification',
        debugShowCheckedModeBanner: false,
        initialRoute: '/',
        theme: ThemeData(
          useMaterial3: true,
        ),
        themeMode: ThemeMode.dark,
        routes: {
          "/": (context) {
            return const TestPage();
          },
        });
  }
}
`

test_page.dart
`import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:test_notification/push_notification_service.dart';

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
  print(message.data.toString());
  print(message.data['type']);
  print(message.data['notification']);
}

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> with WidgetsBindingObserver {
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

  @override
  void initState() {
    super.initState();
    _initializeFirebaseMessaging();
  }

  Future<void> _initializeFirebaseMessaging() async {
    await _firebaseMessaging.requestPermission();
    await _getToken();

    FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      print("Foreground message: ${message.notification?.body.toString()}");
      print(message.data.toString());
      print(message.data['type']);
      print(message.data['notification']);
      print(message.data['display_notification']);
    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      print("Background message: $message");
    });

    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  }

  Future<void> _getToken() async {
    String? fcmToken = await _firebaseMessaging.getToken();
    if (fcmToken == null) return;

    print(fcmToken);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: FilledButton(
          onPressed: () {
            PushNotificationService.sendFcmNotification();
          },
          child: Text('send notification'),
        ),
      ),
    );
  }
}

test_page.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:test_notification/push_notification_service.dart';

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
  print(message.data.toString());
  print(message.data['type']);
  print(message.data['notification']);
}

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> with WidgetsBindingObserver {
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

  @override
  void initState() {
    super.initState();
    _initializeFirebaseMessaging();
  }

  Future<void> _initializeFirebaseMessaging() async {
    await _firebaseMessaging.requestPermission();
    await _getToken();

    FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      print("Foreground message: ${message.notification?.body.toString()}");
      print(message.data.toString());
      print(message.data['type']);
      print(message.data['notification']);
      print(message.data['display_notification']);
    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      print("Background message: $message");
    });

    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  }

  Future<void> _getToken() async {
    String? fcmToken = await _firebaseMessaging.getToken();
    if (fcmToken == null) return;

    print(fcmToken);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: FilledButton(
          onPressed: () {
            PushNotificationService.sendFcmNotification();
          },
          child: Text('send notification'),
        ),
      ),
    );
  }
}

push_notification_service.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:test_notification/push_notification_service.dart';

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
  print(message.data.toString());
  print(message.data['type']);
  print(message.data['notification']);
}

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  @override
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage> with WidgetsBindingObserver {
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;

  @override
  void initState() {
    super.initState();
    _initializeFirebaseMessaging();
  }

  Future<void> _initializeFirebaseMessaging() async {
    await _firebaseMessaging.requestPermission();
    await _getToken();

    FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      print("Foreground message: ${message.notification?.body.toString()}");
      print(message.data.toString());
      print(message.data['type']);
      print(message.data['notification']);
      print(message.data['display_notification']);
    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      print("Background message: $message");
    });

    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  }

  Future<void> _getToken() async {
    String? fcmToken = await _firebaseMessaging.getToken();
    if (fcmToken == null) return;

    print(fcmToken);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: FilledButton(
          onPressed: () {
            PushNotificationService.sendFcmNotification();
          },
          child: Text('send notification'),
        ),
      ),
    );
  }
}
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^3.2.0
  firebase_auth: ^5.1.0
  cloud_firestore: ^5.0.0
  http: ^0.13.6
  dio: ^5.4.3+1
  firebase_messaging: 15.0.3
  googleapis: ^13.1.0
  googleapis_auth: ^1.4.1
SelaseKay commented 2 weeks ago

I'm unable to reproduce this on my end. Kindly provide a screenshot or screen recording of what you see in foreground when you send the push notification.

rrakesh28 commented 2 weeks ago

May i know the flutter version and ios version used for the test.

SelaseKay commented 2 weeks ago

flutter version: 3.24.0 iOS: 17.2