Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 [firebase_messaging] Silent notifications not handled on iOS #8277

Closed
Johandrex opened this issue Mar 15, 2022 · 119 comments · Fixed by #8759
Closed

🐛 [firebase_messaging] Silent notifications not handled on iOS #8277

Johandrex opened this issue Mar 15, 2022 · 119 comments · Fixed by #8759
Assignees
Labels
impact: crowd Affects many people, though not necessarily a specific customer with an assigned label. (P2) platform: ios Issues / PRs which are specifically for iOS. plugin: messaging resolution: fixed A fix has been merged or is pending merge from a PR. type: bug Something isn't working

Comments

@Johandrex
Copy link

Bug report

The issue is regarding silent notifications that are sent from firebase on iOS. When a silent notification is sent through firebase, the android app can handle the notification both in the foreground and background. Silent notifications can however not be handled on iOS, neither in the foreground or background, the app doesn't get any indication of the app receiving the notification.

Steps to reproduce

Steps to reproduce the behavior:

  1. From Postman, test to POST the following content to https://fcm.googleapis.com/fcm/send
    "to": "_token",
    "priority": "high",
    "data": {
        "content-available": 1
    },
  1. This results in the iOS app not receiving the notification, only the android app can receive the silent notification. However when adding the code down below to the Body, the iOS app will receive the notification. It's however not a silent notification that only can be received when the app is in the foreground
    "notification": { "body": "1" }

Expected behavior

The iOS app should be able to handle silent notifications, just like the android app currently can.


Additional context

The bug occurs when using 'firebase_messaging' 11.2.10, flutter 12.2.1 on iOS 15.3.1


Flutter doctor

Run flutter doctor and paste the output below:

Click To Expand
/Users/user/Development/flutter/bin/flutter doctor --verbose
[✓] Flutter (Channel stable, 2.10.3, on macOS 12.2.1 21D62 darwin-x64, locale en-SE)
    • Flutter version 2.10.3 at /Users/user/Development/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 7e9793dee1 (13 days ago), 2022-03-02 11:23:12 -0600
    • Engine revision bd539267b4
    • Dart version 2.16.1
    • DevTools version 2.9.2

[!] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
    • Android SDK at /Users/user/Library/Android/sdk
    ✗ cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.

[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.11.2

[✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[✓] Android Studio (version 2021.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)

[✓] Connected device (1 available)
    • Snow (mobile) • XXXX-XXXX• ios • iOS 15.3.1 19D52

[✓] HTTP Host Availability
    • All required HTTP hosts are available

! Doctor found issues in 2 categories.
Process finished with exit code 0

Flutter dependencies

Run flutter pub deps -- --style=compact and paste the output below:

Click To Expand
dependencies:
- app_settings 4.1.1 [flutter]
- connectivity_plus 2.2.1 [flutter connectivity_plus_platform_interface connectivity_plus_linux connectivity_plus_macos connectivity_plus_web connectivity_plus_windows]
- cupertino_icons 1.0.4
- device_info_plus 3.2.2 [flutter device_info_plus_platform_interface device_info_plus_macos device_info_plus_linux device_info_plus_web device_info_plus_windows]
- dio 4.0.4 [http_parser path]
- enum_to_string 2.0.1
- firebase_analytics 9.1.2 [firebase_analytics_platform_interface firebase_analytics_web firebase_core firebase_core_platform_interface flutter]
- firebase_core 1.13.1 [firebase_core_platform_interface firebase_core_web flutter meta]
- firebase_crashlytics 2.5.3 [firebase_core firebase_core_platform_interface firebase_crashlytics_platform_interface flutter stack_trace]
- firebase_messaging 11.2.10 [firebase_core firebase_core_platform_interface firebase_messaging_platform_interface firebase_messaging_web flutter meta]
- firebase_performance 0.8.0+7 [firebase_core firebase_core_platform_interface firebase_performance_platform_interface firebase_performance_web flutter]
- flutter 0.0.0 [characters collection material_color_utilities meta typed_data vector_math sky_engine]
- flutter_spinbox 0.8.0 [cupertino_icons flutter meta]
- flutter_svg 1.0.3 [flutter meta path_drawing vector_math xml]
- hand_signature 2.1.1 [flutter flutter_svg]
- http 0.13.4 [async http_parser meta path]
- image_picker 0.8.4+10 [flutter flutter_plugin_android_lifecycle image_picker_for_web image_picker_platform_interface]
- intl 0.17.0 [clock path]
- maps_launcher 2.0.1 [flutter flutter_web_plugins url_launcher]
- provider 6.0.2 [collection flutter nested]
- qr_code_scanner 0.7.0 [js flutter flutter_web_plugins]
- shared_preferences 2.0.13 [flutter shared_preferences_android shared_preferences_ios shared_preferences_linux shared_preferences_macos shared_preferences_platform_interface shared_preferences_web shared_preferences_windows]
- timer_builder 2.0.0 [flutter]
- uuid 3.0.6 [crypto]

dev dependencies:
- flutter_lints 1.0.4 [lints]
- flutter_test 0.0.0 [flutter test_api path fake_async clock stack_trace vector_math async boolean_selector characters charcode collection matcher material_color_utilities meta source_span stream_channel string_scanner term_glyph typed_data]

transitive dependencies:
- args 2.3.0
- async 2.8.2 [collection meta]
- boolean_selector 2.1.0 [source_span string_scanner]
- characters 1.2.0
- charcode 1.3.1
- clock 1.1.0
- collection 1.15.0
- connectivity_plus_linux 1.3.0 [flutter connectivity_plus_platform_interface meta nm]
- connectivity_plus_macos 1.2.1 [connectivity_plus_platform_interface flutter]
- connectivity_plus_platform_interface 1.2.0 [flutter meta plugin_platform_interface]
- connectivity_plus_web 1.2.0 [connectivity_plus_platform_interface flutter_web_plugins flutter]
- connectivity_plus_windows 1.2.0 [connectivity_plus_platform_interface flutter]
- cross_file 0.3.2 [flutter js meta]
- crypto 3.0.1 [collection typed_data]
- dbus 0.7.1 [args ffi meta xml]
- device_info_plus_linux 2.1.1 [device_info_plus_platform_interface file flutter meta]
- device_info_plus_macos 2.2.2 [device_info_plus_platform_interface flutter]
- device_info_plus_platform_interface 2.3.0+1 [flutter meta plugin_platform_interface]
- device_info_plus_web 2.1.0 [device_info_plus_platform_interface flutter_web_plugins flutter]
- device_info_plus_windows 2.1.1 [device_info_plus_platform_interface ffi flutter win32]
- fake_async 1.2.0 [clock collection]
- ffi 1.1.2
- file 6.1.2 [meta path]
- firebase 9.0.2 [http http_parser js]
- firebase_analytics_platform_interface 3.1.1 [firebase_core flutter meta plugin_platform_interface]
- firebase_analytics_web 0.4.0+8 [firebase_analytics_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins js]
- firebase_core_platform_interface 4.2.5 [collection flutter meta plugin_platform_interface]
- firebase_core_web 1.6.1 [firebase_core_platform_interface flutter flutter_web_plugins js meta]
- firebase_crashlytics_platform_interface 3.2.1 [collection firebase_core flutter meta plugin_platform_interface]
- firebase_messaging_platform_interface 3.2.1 [firebase_core flutter meta plugin_platform_interface]
- firebase_messaging_web 2.2.9 [firebase_core firebase_core_web firebase_messaging_platform_interface flutter flutter_web_plugins js meta]
- firebase_performance_platform_interface 0.1.1+1 [firebase_core flutter plugin_platform_interface]
- firebase_performance_web 0.1.0+7 [firebase firebase_core firebase_core_web firebase_performance_platform_interface flutter flutter_web_plugins js]
- flutter_plugin_android_lifecycle 2.0.5 [flutter]
- flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta typed_data vector_math]
- http_parser 4.0.0 [charcode collection source_span string_scanner typed_data]
- image_picker_for_web 2.1.6 [flutter flutter_web_plugins image_picker_platform_interface]
- image_picker_platform_interface 2.4.4 [cross_file flutter http plugin_platform_interface]
- js 0.6.3
- lints 1.0.1
- matcher 0.12.11 [stack_trace]
- material_color_utilities 0.1.3
- meta 1.7.0
- nested 1.0.0 [flutter]
- nm 0.5.0 [dbus]
- path 1.8.0
- path_drawing 1.0.0 [vector_math meta path_parsing flutter]
- path_parsing 1.0.0 [vector_math meta]
- path_provider_linux 2.1.5 [ffi flutter path path_provider_platform_interface xdg_directories]
- path_provider_platform_interface 2.0.3 [flutter platform plugin_platform_interface]
- path_provider_windows 2.0.5 [ffi flutter path path_provider_platform_interface win32]
- petitparser 4.4.0 [meta]
- platform 3.1.0
- plugin_platform_interface 2.1.2 [meta]
- process 4.2.4 [file path platform]
- shared_preferences_android 2.0.11 [flutter shared_preferences_platform_interface]
- shared_preferences_ios 2.1.0 [flutter shared_preferences_platform_interface]
- shared_preferences_linux 2.1.0 [file flutter path path_provider_linux path_provider_platform_interface shared_preferences_platform_interface]
- shared_preferences_macos 2.0.3 [flutter shared_preferences_platform_interface]
- shared_preferences_platform_interface 2.0.0 [flutter]
- shared_preferences_web 2.0.3 [flutter flutter_web_plugins shared_preferences_platform_interface]
- shared_preferences_windows 2.1.0 [file flutter path path_provider_platform_interface path_provider_windows shared_preferences_platform_interface]
- sky_engine 0.0.99
- source_span 1.8.1 [collection path term_glyph]
- stack_trace 1.10.0 [path]
- stream_channel 2.1.0 [async]
- string_scanner 1.1.0 [charcode source_span]
- term_glyph 1.2.0
- test_api 0.4.8 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph matcher]
- typed_data 1.3.0 [collection]
- url_launcher 6.0.20 [flutter url_launcher_android url_launcher_ios url_launcher_linux url_launcher_macos url_launcher_platform_interface url_launcher_web url_launcher_windows]
- url_launcher_android 6.0.15 [flutter url_launcher_platform_interface]
- url_launcher_ios 6.0.15 [flutter url_launcher_platform_interface]
- url_launcher_linux 3.0.0 [flutter url_launcher_platform_interface]
- url_launcher_macos 3.0.0 [flutter url_launcher_platform_interface]
- url_launcher_platform_interface 2.0.5 [flutter plugin_platform_interface]
- url_launcher_web 2.0.9 [flutter flutter_web_plugins url_launcher_platform_interface]
- url_launcher_windows 3.0.0 [flutter url_launcher_platform_interface]
- vector_math 2.1.1
- win32 2.4.1 [ffi]
- xdg_directories 0.2.0+1 [meta path process]
- xml 5.3.1 [collection meta petitparser]

@Johandrex Johandrex added Needs Attention This issue needs maintainer attention. type: bug Something isn't working labels Mar 15, 2022
@darshankawar darshankawar added the triage Issue is currently being triaged. label Mar 15, 2022
@darshankawar
Copy link

@Johandrex
Can you try by setting following:

content-available: true
apns-priority: 5 (for e.g)

And see if it helps ?

@darshankawar darshankawar added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. labels Mar 15, 2022
@Johandrex
Copy link
Author

Johandrex commented Mar 15, 2022

apns-priority

Sadly, neither

{
    "to": "_token",
    "priority": "high",
    "content-available": true,
    "apns-priority": 5,
    "data": {
        "content-available": 1
    }
}

nor

{
    "to": "_token",
    "priority": "high",
    "data": {
        "content-available": true,
        "apns-priority": 5
    }
}

works.

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Mar 15, 2022
@darshankawar
Copy link

@Johandrex
Since you are trying on iOS, can you try using getAPNSToken() ?

Also, do make sure you are handling foreground notifications as mentioned in the document: https://firebase.flutter.dev/docs/messaging/notifications#foreground-notifications

@darshankawar darshankawar added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. labels Mar 16, 2022
@Johandrex
Copy link
Author

@Johandrex Since you are trying on iOS, can you try using getAPNSToken() ?

Also, do make sure you are handling foreground notifications as mentioned in the document: https://firebase.flutter.dev/docs/messaging/notifications#foreground-notifications

Thanks for the answer, getAPNSToken() does indeed result in a token. But this can't be sent to firebase through postman as it returns the following error
"error": "InvalidRegistration"

The foreground notifications are handled in the same way as the link explains.

Also, I just want to reiterate that the notifications does reach the phone when "notification": { "body": "1" } is set. However, when removing notification it's not handled by the plugin on iOS, only Android.

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Mar 16, 2022
@darshankawar
Copy link

However, when removing notification it's not handled by the plugin on iOS, only Android.

Ok, this is a new update for me based on our conversation so far :-) which has given a new angle to the issue, probably I was trying to give you solutions from a different perspective.

Also, how is Android behavior when it comes to handling this behavior ?

@darshankawar darshankawar added blocked: customer-response Waiting for customer response, e.g. more information was requested. and removed Needs Attention This issue needs maintainer attention. labels Mar 17, 2022
@Johandrex
Copy link
Author

Johandrex commented Mar 17, 2022

However, when removing notification it's not handled by the plugin on iOS, only Android.

Ok, this is a new update for me based on our conversation so far :-) which has given a new angle to the issue, probably I was trying to give you solutions from a different perspective.

Also, how is Android behavior when it comes to handling this behavior ?

Sorry, might not've explained it clearly enough. The Android behavior works as expected. When receiving the silent notification on Android it's being handled by the app in both the foreground and background.

But when receiving the exact same type of silent notification on iOS there's no indication of the app receiving the silent notification.

See the notification service I have created down below.


import 'dart:async';
import 'dart:io';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';

class NotificationService {
  static final _firebaseMessaging = FirebaseMessaging.instance;

  final notificationController = StreamController<RemoteMessage>.broadcast();

  NotificationService() {
    FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
    FirebaseMessaging.onMessage.listen(
      (message) async {
        print("Notification Received");
        notificationController.sink.add(message);
      },
    );
  }

  dispose() {
    notificationController.close();
  }
}

Future<void> onBackgroundMessage(RemoteMessage message) async {
  await Firebase.initializeApp();

  print("Notification Received");

  // handle the notification
}

@google-oss-bot google-oss-bot added Needs Attention This issue needs maintainer attention. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Mar 17, 2022
@darshankawar
Copy link

Thanks for the update.
I am labeling this for further insights from the team.

/cc @russellwheatley

@darshankawar darshankawar added plugin: messaging platform: ios Issues / PRs which are specifically for iOS. and removed Needs Attention This issue needs maintainer attention. triage Issue is currently being triaged. labels Mar 18, 2022
@VKBobyr
Copy link

VKBobyr commented Mar 18, 2022

I'm having the same issues on firebase_messaging: ^11.2.11
Though downgrading to 9.0.0 doesn't help either

Upgrading my Flutter SDK didn't help either.

Code:

FirebaseMessaging.onMessage.listen((message) async {
  Log.info(message);
  Log.info(message.notification?.title);
  Log.info(message.notification?.body);
  Log.info(message.data);

  // ...
});

It works with non-data-only notifications. But data-only never trigger the .listen callback

@Faaatman
Copy link

Faaatman commented Mar 20, 2022

I had to downgrade to firebase_messaging: 11.2.8 (The previous version I was on) which worked for me. I haven't tested the other versions.

@Johandrex
Copy link
Author

I had to downgrade to firebase_messaging: 11.2.8 (The previous version I was on) which worked for me. I haven't tested the other versions.

Will try this shortly, how did the notification you sent to firebase look like? Did it contain a 'data' field etc? Does the silent notification work in both the background and foreground?

@iosephmagno
Copy link

iosephmagno commented Jun 7, 2022

They would display if "content_available:true", this way they can be used as alternative to "notification" when they are required (eg. an encrypted payload that must be decrypted before being showed in the notification badge).

We need to understand if it is an issue of firebase messaging not triggering foreground and background method or it is an issue of flutter local notification. We have no way to figure this out.

@russellwheatley
Copy link
Member

So, if I understand you correctly, you're receiving the message in the message stream as shown in the video clip. But flutter local notification is failing to show a notification?

In my instance, in the foreground, I received the message in the message stream and also had a local notification pop up as I implemented it. Of course, I had to remove the if statement here in the example.

For background silent notifications, I was still able to use local flutter notifications to trigger a pop up notification.

@iosephmagno
Copy link

iosephmagno commented Jun 7, 2022

@russellwheatley can you paste here the main? We are not able to receive background notifications

I mean a link to the file we can download on googledrive or elsewhere.

@russellwheatley
Copy link
Member

I'm running flutter run --release. I used the example app, and stripped everything out of onMessage and displayed a local notification using flutterLocalNotificationsPlugin.show(). Put flutterLocalNotificationsPlugin.show() in onBackgroundMessage as well.

@iosephmagno
Copy link

@russellwheatley can you send the main somehow?

@abdulrehmank7
Copy link

#8277 (comment)

We are using below notification body

{
    "to": "dv9to4YukE0cu2mUSQDcGB:APA91bF3W6LCc3GKL0tetEJdaHGwmnNhiM6c0ovS0gPPWObgPYtiBASLrttOyE0ahukmBZx33DDwFdTtyLaUz8x7PJPVUFoZnZxPfmPGYFnwcYsJZAgd03WObI9iXifHG3mum0-vXX33",
    "notification": {
        "title": "Testing non data Notification",
        "body": "Introducing yumms - your daily dose of exciting food content in one click from our top chefs👩‍🍳 🥘Click and tap explore.",
        "sound": "default"    
    },
    "data": {
        "title": "Testing data Notification",
        "message": "Introducing yumms - your daily dose of exciting food content in one click from our top chefs👩‍🍳 🥘Click and tap explore.",
        "deeplink": "www.sortizy.com/id/100",
                "click_action": "FLUTTER_NOTIFICATION_CLICK"

    },
    "apns": {
        "headers": {
            "apns-priority": "5"
        }
    },
    "content_available": true,
    "priority": "high"  
}

We send notification from our server to both android and iOS users.
The above body works as expected in both android and iOS in firebase_messaging: 11.2.8. In background and foreground, onMessageOpenedApp is triggered and notification is also shown. We just handle the data in onMessageOpenedApp and execute our app flow.

In firebase_messaging: ^11.4.1 on iOS, onMessageOpenedApp is never triggered in background or foreground. We are able to see notification. On clicking notification onMessageOpenedApp is not triggered.
The same flow works in 11.2.8 then why it is breaking in latest version of plugin. We have kept the plugin version on firebase_messaging: 11.2.8 and we are not facing any issues.
On updating the plugin then we still receive notification but onMessageOpenedApp is not triggered.

Let us know if the above-mentioned issue is different from current ticket, we will file a new ticket if that is the case.

@iosephmagno
Copy link

iosephmagno commented Jun 7, 2022

@abdulrehmank7 yes it is different. To have Silent Notifications only you should remove this.

"notification": {
"title": "Testing non data Notification",
"body": "Introducing yumms.....",
"sound": "default"
},

We personally receive notification with no issue if we add "notification" in the payload, but as said, that is not a silent notification.

@iosephmagno
Copy link

@russellwheatley doesnt work. Can you please share file zipped somewhere? Thx

@abdulrehmank7
Copy link

@abdulrehmank7 yes it is different. To have Silent Notifications only you should remove this.

"notification": { "title": "Testing non data Notification", "body": "Introducing yumms.....", "sound": "default" },

We personally receive notification with no issue if we add "notification" in the payload, but as said, that is not a silent notification.

So we need to send body without notification tag for onMessageOpenedAppto work?
If that is the case, then we need to send different body for android and iOS user?

@iosephmagno
Copy link

iosephmagno commented Jun 7, 2022

@russellwheatley Here is our custom main everyone can paste inside firebase message example

https://drive.google.com/file/d/10uHILwLnGPz2cCvuhi3uejt_jLeiO3If/view?usp=sharing

Report is:

  • flutter version 2.10.5
  • ios version 15.4.1
  • Background Silent notification not working in release build
  • Foreground Silent notification working in release build

We don't think it is flutter_local_notification issue because _firebaseMessagingBackgroundHandler is never triggered.
Can you please test it and let us know?

Payload is:
{
"to": "TOKEN",
"content_available": true,
"data": {
"title": "Silent Notification"
"body": "Hello World!"
},
"aps" : {
"content_available" : true
},
"apns-push-type": "bacground",
"apns-priority": "5",
"apns-topic": "com.*****.presence"
}

@russellwheatley
Copy link
Member

Just tested again this morning. Here is main.dart for example app code that shows flutter notification from background state in release mode using silent notification (data only object). I literally just put flutterLocalNotificationsPlugin.show() into the background handler:

// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

import 'message.dart';
import 'message_list.dart';
import 'permissions.dart';
import 'token_monitor.dart';
import 'firebase_options.dart';

/// Define a top-level named handler which background/terminated messages will
/// call.
///
/// To verify things are working, check out the native platform logs.
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await flutterLocalNotificationsPlugin.show(
    2,
    'notification.title',
    message.data.toString(),
    NotificationDetails(
      android: AndroidNotificationDetails(
        channel.id,
        channel.name,
        channel.description,
        // TODO add a proper drawable resource to android, for now using
        //      one that already exists in example app.
        icon: 'launch_background',
      ),
    ),
  );
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  print('Handling a background message ${message.messageId}');
}

/// Create a [AndroidNotificationChannel] for heads up notifications
late AndroidNotificationChannel channel;

/// Initialize the [FlutterLocalNotificationsPlugin] package.
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // Set the background messaging handler early on, as a named top-level function
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  if (!kIsWeb) {
    channel = const AndroidNotificationChannel(
      'high_importance_channel', // id
      'High Importance Notifications', // title
      'This channel is used for important notifications.', // description
      importance: Importance.high,
    );

    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

    /// Create an Android Notification Channel.
    ///
    /// We use this channel in the `AndroidManifest.xml` file to override the
    /// default FCM channel to enable heads up notifications.
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    /// Update the iOS foreground notification presentation options to allow
    /// heads up notifications.
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );
  }

  runApp(MessagingExampleApp());
}

/// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Messaging Example App',
      theme: ThemeData.dark(),
      routes: {
        '/': (context) => Application(),
        '/message': (context) => MessageView(),
      },
    );
  }
}

// Crude counter to make messages unique
int _messageCount = 0;

/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
String constructFCMPayload(String? token) {
  _messageCount++;
  return jsonEncode({
    'token': token,
    'data': {
      'via': 'FlutterFire Cloud Messaging!!!',
      'count': _messageCount.toString(),
    },
    'notification': {
      'title': 'Hello FlutterFire!',
      'body': 'This notification (#$_messageCount) was created via FCM!',
    },
  });
}

/// Renders the example application.
class Application extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  String? _token;

  @override
  void initState() {
    super.initState();
    FirebaseMessaging.instance
        .getInitialMessage()
        .then((RemoteMessage? message) {
      if (message != null) {
        Navigator.pushNamed(
          context,
          '/message',
          arguments: MessageArguments(message, true),
        );
      }
    });

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      AndroidNotification? android = message.notification?.android;

        flutterLocalNotificationsPlugin.show(
          1,
          'notification.title',
          message.data.toString(),
          NotificationDetails(
            android: AndroidNotificationDetails(
              channel.id,
              channel.name,
              channel.description,
              // TODO add a proper drawable resource to android, for now using
              //      one that already exists in example app.
              icon: 'launch_background',
            ),
          ),
        );

    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');
      Navigator.pushNamed(
        context,
        '/message',
        arguments: MessageArguments(message, true),
      );
    });
  }

  Future<void> sendPushMessage() async {
    if (_token == null) {
      print('Unable to send FCM message, no token exists.');
      return;
    }

    try {
      await http.post(
        Uri.parse('https://api.rnfirebase.io/messaging/send'),
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
        },
        body: constructFCMPayload(_token),
      );
      print('FCM request for device sent!');
    } catch (e) {
      print(e);
    }
  }

  Future<void> onActionSelected(String value) async {
    switch (value) {
      case 'subscribe':
        {
          print(
            'FlutterFire Messaging Example: Subscribing to topic "fcm_test".',
          );
          await FirebaseMessaging.instance.subscribeToTopic('fcm_test');
          print(
            'FlutterFire Messaging Example: Subscribing to topic "fcm_test" successful.',
          );
        }
        break;
      case 'unsubscribe':
        {
          print(
            'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test".',
          );
          await FirebaseMessaging.instance.unsubscribeFromTopic('fcm_test');
          print(
            'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test" successful.',
          );
        }
        break;
      case 'get_apns_token':
        {
          if (defaultTargetPlatform == TargetPlatform.iOS ||
              defaultTargetPlatform == TargetPlatform.macOS) {
            print('FlutterFire Messaging Example: Getting APNs token...');
            String? token = await FirebaseMessaging.instance.getAPNSToken();
            print('FlutterFire Messaging Example: Got APNs token: $token');
          } else {
            print(
              'FlutterFire Messaging Example: Getting an APNs token is only supported on iOS and macOS platforms.',
            );
          }
        }
        break;
      default:
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cloud Messaging'),
        actions: <Widget>[
          PopupMenuButton(
            onSelected: onActionSelected,
            itemBuilder: (BuildContext context) {
              return [
                const PopupMenuItem(
                  value: 'subscribe',
                  child: Text('Subscribe to topic'),
                ),
                const PopupMenuItem(
                  value: 'unsubscribe',
                  child: Text('Unsubscribe to topic'),
                ),
                const PopupMenuItem(
                  value: 'get_apns_token',
                  child: Text('Get APNs token (Apple only)'),
                ),
              ];
            },
          ),
        ],
      ),
      floatingActionButton: Builder(
        builder: (context) => FloatingActionButton(
          onPressed: sendPushMessage,
          backgroundColor: Colors.white,
          child: const Icon(Icons.send),
        ),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            MetaCard('Permissions', Permissions()),
            MetaCard(
              'FCM Token',
              TokenMonitor((token) {
                _token = token;
                return token == null
                    ? const CircularProgressIndicator()
                    : Text(token, style: const TextStyle(fontSize: 12));
              }),
            ),
            MetaCard('Message Stream', MessageList()),
          ],
        ),
      ),
    );
  }
}

/// UI Widget for displaying metadata.
class MetaCard extends StatelessWidget {
  final String _title;
  final Widget _children;

  // ignore: public_member_api_docs
  MetaCard(this._title, this._children);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
      child: Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              Container(
                margin: const EdgeInsets.only(bottom: 16),
                child: Text(_title, style: const TextStyle(fontSize: 18)),
              ),
              _children,
            ],
          ),
        ),
      ),
    );
  }
}

@iosephmagno
Copy link

iosephmagno commented Jun 8, 2022

@russellwheatley Curiosity, what happens on your device with my main?

Tested your main, same result:

  • flutter version 2.10.5
  • ios version 15.4.1
  • Background Silent notification not working in release build
  • Foreground Silent notification working in release build

@iosephmagno
Copy link

Which flutter / ios versions are you testing with?

@russellwheatley
Copy link
Member

Flutter doctor:

iOS 15.5

[✓] Flutter (Channel stable, 3.0.0, on macOS 12.3.1 21E258 darwin-x64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] IntelliJ IDEA Community Edition (version 2020.1.1)
[✓] VS Code (version 1.67.2)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

@iosephmagno
Copy link

iosephmagno commented Jun 8, 2022

Tested on other iphone with os 15.5 and it works for me as well.
So issue is with 15.4.1 and maybe other specific versions of ios.
Firebase team should debug this issue carefully. We cannot do that with our resources.

IMG_7927.mov

@iosephmagno
Copy link

@russellwheatley Also, behaviour is strange. Notifications work in background only after a first one is received in foreground. That is a bug surely.

@iosephmagno
Copy link

Good news to everyone scourged by this issue.
Basically silent notifications work with both mine main and Russel’s main.

When they dont work, try this:

  • Disable update app in background from Settings
  • Enable it again
  • Reboot system

This fixed the issue on iphone running 15.4.1.

@anton-brass
Copy link

@iosephmagno where is your main? And what did you guys change that it is working now and did not before? Will there be a fix of firebase_messaging itself then or was it some kind of configuration in the receiving app?

Had the same issue with firebase_messaging: ^11.2.12 on iOS and did not debug further yet.

@dehypnosis
Copy link

dehypnosis commented Jun 10, 2022

If you have some trouble with data only message (silent message) in iOS.
Try to set GoogleUtilitiesAppDelegateProxyEnabled as NO in Info.plist file.

I guess that GoogleUtilities/AppDelegateSwizzler may block FirebaseMessaging app delegate.. in order to cause not invoking didReceiveRemoteNotification methods.

Fixed current problem by above workaround in foreground/background in iOS 15.5, firebase_messaging 11.4.1.

@Johandrex
Copy link
Author

Johandrex commented Jun 10, 2022

It's finally working!!! Thank you so much @russellwheatley.

The reason why it didn't work before was because I was setting "contentAvailable": true, when you're supposed to set the name it as "content_available": true..

Working on iOS 15.5, Flutter 2, firebase_messaging 11.4.1.

Here's the working data-only payload, which works on iOS both in the foreground and background if anyone want to test it with postman (POST, url: https://fcm.googleapis.com/fcm/send).


{
    "to": "_deviceToken",
    "data": {
        "clear_data": true
    },
    "content_available": true,
    "apns-priority": 5
}

My NotificationService


import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_preferences_android/shared_preferences_android.dart';
import 'package:shared_preferences_ios/shared_preferences_ios.dart';

class NotificationService {
  final _firebaseMessaging = FirebaseMessaging.instance;

  NotificationService() {
    FirebaseMessaging.onBackgroundMessage(onBackgroundNotification);
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async => _onMessageOpenedApp(message));
    FirebaseMessaging.onMessage.listen((RemoteMessage message) async => _onMessage(message));

    _firebaseMessaging.getToken().then((value) {
      print('Push Notification Token: $value');
    });
  }

  _onMessageOpenedApp(RemoteMessage message) async {
    print('Tapped notification');
    print('Notification received in the foreground');
    print('Contains data: ${message.data.toString()}');
  }

  _onMessage(RemoteMessage message) async {
    print('Notification received in the foreground');
    print('Contains data: ${message.data.toString()}');
  }

  /// Check if any notifications was received when the app was in the background.
  checkBackgroundNotification() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.reload();
    String? _message = prefs.getString('notification');

    if (_message == null) return;

    Map<String, dynamic> _data = json.decode(_message);
    // _handle(_data);
    await prefs.remove('notification');
  }
}

Future<void> onBackgroundNotification(RemoteMessage message) async {
  WidgetsFlutterBinding.ensureInitialized();

  print('Notification received in the background');
  print('Contains data: ${message.data.toString()}');

  if (Platform.isAndroid) SharedPreferencesAndroid.registerWith();
  if (Platform.isIOS) SharedPreferencesIOS.registerWith();

  final prefs = await SharedPreferences.getInstance();
  await prefs.reload();
  String? _existingNotification = prefs.getString('notification');

  if (_existingNotification != null) {
    Map<String, dynamic> _data = json.decode(_existingNotification);
    message.data.forEach((key, value) {
      _data[key] = value;
    });

    await prefs.setString('notification', json.encode(_data));
  } else {
    await prefs.setString('notification', json.encode(message.data));
  }
}

@darshankawar darshankawar added resolution: fixed A fix has been merged or is pending merge from a PR. and removed blocked: customer-response Waiting for customer response, e.g. more information was requested. labels Jun 20, 2022
alexmercerind added a commit to alexmercerind/bluecherry-mobile-notification-hook that referenced this issue Jun 28, 2022
DVR warnings have higher priority.
We need the application to be "awaken" from background for sending notifications from Flutter, because we are only consuming the data from payload (and not notification). contentAvailable: true causes inactive client app on iOS to be awoken. On Android, data messages wake the app by default.
priority: 'high' makes the data to be received immediately on the client app. By default, data-only payload shows with priority: 'normal' (causes some unspecified delay, battery-consumption etc). Since, notification key is now removed from the payload, it is important to set the priority to `high` explicitly.
A discussion regarding this can be seen at:
firebase/flutterfire#8277 (comment)
https://firebase.google.com/docs/cloud-messaging/http-server-ref
@firebase firebase locked and limited conversation to collaborators Jul 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
impact: crowd Affects many people, though not necessarily a specific customer with an assigned label. (P2) platform: ios Issues / PRs which are specifically for iOS. plugin: messaging resolution: fixed A fix has been merged or is pending merge from a PR. type: bug Something isn't working
Projects
None yet