Flutter FCM NOT handling messages when app terminated on IOS
Asked Answered
E

4

5

The context: I have a flutter app in which it is my goal to handle push notifications messages with firebase messaging to users who are subscribed to a topic. The notification is sent with data that is saved locally using SharedPreferences in the handlers. This works in 5 out of 6 scenario's.

This scenario works on Android and IOS both when:

  1. The app is on the foreground, the handler is called and data is stored.
  2. The app is on the background but not killed/terminated. The handler is called and data is stored.
  3. [Android only] The app is terminated from the open apps view.

The one scenario in which the onBackgroundMessage() handler is not called (even though the push notification comes through and is displayed), is on IOS when the app is terminated. The handler does however work when you send a second push notification right after the first one, because the first one wakes up the app.

I even tried using the getInitialMessage() to get a message after opening the app which I put in my main() function. However that handler isn't called either. So I currently have no way of handling data in the flow which I get from firebase push notifications on IOS when the app is terminated, even though the user does receive a push notification.

What I already have done:

  • Everything described here until the advanced steps (I didn't need custom image in my use case)
  • I also enabled Background processing just to be sure.
  • Tested using real devices, not emulators.
  • Ran the app in release mode using flutter run --release

Here is the main and the handlers:

Future<void> firebaseMessagingForegroundHandler(
    RemoteMessage message, BuildContext context) async {
  // print("Handling a foreground message: ${message.notification?.title}");
  var prefs = await SharedPreferences.getInstance();

  var unreadMessages = prefs.getString('unreadMessages') ?? "";
  var readMessages = prefs.getString('readMessages') ?? "";
  int lastIndex = await getLastTabIndex();

  if (lastIndex == 2) {
    if (readMessages.isEmpty) {
      prefs.setString('readMessages', jsonEncode([message.data]));
    } else {
      var listWithReadMessages = jsonDecode(readMessages);
      listWithReadMessages.add(message.data);
      prefs.setString('readMessages', jsonEncode(listWithReadMessages));
    }
  } else {
    if (unreadMessages.isEmpty) {
      prefs.setString('unreadMessages', jsonEncode([message.data]));
    } else {
      var listWithUnreadMessages = jsonDecode(unreadMessages);
      listWithUnreadMessages.add(message.data);
      prefs.setString('unreadMessages', jsonEncode(listWithUnreadMessages));
    }
  }

  ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Nieuwe melding van ${message.data['sender']}')));
}

@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  debugPrint("Handling a background message: ${message.messageId}");
  //To ensure the sharedpreferences plugin is loaded in.
  DartPluginRegistrant.ensureInitialized();
  var prefs = await SharedPreferences.getInstance();
  prefs.reload();

  var unreadMessages = prefs.getString('unreadMessages');
  if (unreadMessages == null) {
    prefs.setString('unreadMessages', jsonEncode([message.data]));
  } else {
    var listWithUnreadMessages = jsonDecode(unreadMessages);
    listWithUnreadMessages.add(message.data);
    prefs.setString('unreadMessages', jsonEncode(listWithUnreadMessages));
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  var messaging = FirebaseMessaging.instance;
  await messaging.requestPermission(
    alert: true,
    badge: true,
    sound: true,
  );

  FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);

  await messaging
      .getInitialMessage()
      .then((message) => firebaseMessagingBackgroundHandler);

  runApp(const ProviderScope(child: MyApp()));
}

I run the FirebaseMessaging.onMessage.listen((message) from a different widget in the initState() not in my main().

Elkeelkhound answered 12/5, 2023 at 15:55 Comment(4)
did you find any solution for this? i have exactly the same issue!!Khabarovsk
@TabarekGhassan Sadly no. See my reply on the first answer. I took a different approach.Elkeelkhound
Any solutions yet ?Oringa
@RagehAzzazy nope.Elkeelkhound
C
6

Firebase FCM While App Closed Terminated:

  • Notification to get from background while app is closed terminated.
  • Need to run in terminal
1- clean build cache files
flutter clean
flutter pub get

2- must to run from termnial as "realase" mode
flutter run --release

Collazo answered 23/10, 2023 at 8:12 Comment(1)
This is the first thing I tried and didn't the described issue.Elkeelkhound
C
2

There are a few preconditions which must be met before the application can receive message payloads via FCM:

  • The application must have opened at least once (to allow for registration with FCM).
  • On iOS, if the user swipes away the application from the app switcher, it must be manually reopened for background messages to start working again.
  • On Android, if the user force-quits the app from device settings, it must be manually reopened for messages to start working.
  • On web, you must have requested a token (using getToken()) with your web push certificate.

Emphasis by me. Source.

Capper answered 13/5, 2023 at 16:47 Comment(3)
Yes. Those conditions were met. However! On IOS, the firebase push notification will wake up the app and the second push notification sent, will be processed by the onBackgroundMessage() function. Even when the app was terminated from the App switcher. Because of this, as I have not found any solution to process data when receiving push notifications when the app is terminated on IOS, I took a different approach. I am forced to retrieve the required data from the server after opening the app instead of depending on the data sent with the push notification..Elkeelkhound
this is happening to me also, but I wonder how other apps are able to send me notifications on IOS while those apps are terminated, is it because of firebase ? or flutter ? there should be a way to receive notifications while the app is terminatedOringa
i can receive notifications when app is killed using OneSignalBouldin
S
0

A bit late, but maybe it will help someone else. I recently ran in to this exact issue, the ios app waking up and calling the getInitalMessage with a null message. I got logic where i want to store the notification and show them as unread which didnt work since the notification came as null. One minor change to the payload fixed everything, we use Amazon SNS with firebase messaging. :

{"GCM":"{\"notification\":{\"content_available\":1 <-- changed this to 1 instead of true, not sure if the property is needed at all, but it worked,\"title\":\"TITLE\",\"body\":\"Some body content\"},\"data\":{\"click_action\":\"FLUTTER_NOTIFICATION_CLICK\",\"screen\":\"notifications_screen\"}}"}
Spoor answered 27/11, 2023 at 9:32 Comment(3)
Hi, does it work on iOS TERMINATED/killed iOS ????Ganger
@Ganger yeah this was for the terminated stateSpoor
A little different to me, but also worked (terminated state) ... apns: { payload: { aps: { 'content-available': 1, 'mutable-content': 1 } }, }Seizing
A
0

onBackground : will only handle the messages if is running but minimized On Terminated state: to handle user interaction with a notification, use setupInteractedMessage() to receive the 'data' object along with taped notification.

class Application extends StatefulWidget { @override State createState() => _Application(); }

class _Application extends State<Application> {
  // It is assumed that all messages contain a data field with the key 'type'
  Future<void> setupInteractedMessage() async {
    // Get any messages which caused the application to open from
    // a terminated state.
    RemoteMessage? initialMessage =
        await FirebaseMessaging.instance.getInitialMessage();

    // If the message also contains a data property with a "type" of "chat",
    // navigate to a chat screen
    if (initialMessage != null) {
      _handleMessage(initialMessage);
    }

    // Also handle any interaction when the app is in the background via a
    // Stream listener
    FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
  }

  void _handleMessage(RemoteMessage message) {
    if (message.data['type'] == 'chat') {
      Navigator.pushNamed(context, '/chat',
        arguments: ChatArguments(message),
      );
    }
  }

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

    // Run code required to handle interacted messages in an async function
    // as initState() must not be async
    setupInteractedMessage();
  }

  @override
  Widget build(BuildContext context) {
    return Text("...");
  }
}
Acclamation answered 4/4, 2024 at 22:16 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.