CybeX
CybeX

Reputation: 2396

FCM data messages not being received by iOS Simulator or physical device

A project I'm working on requires the use of FCM Push Notifications & Messages for VoIP control amongst others.

Problem:

My iOS physical device receives push notifications, but only when the app is in the background or closed but NOT when in the foreground. Further, it does NOT receive any fcm message (data) notifications at all such as messages with only data and no notification title/body i.e. VoIP status message.


My setup steps are as follows (a detailed setup and general flow I followed can be found here):

  1. Register a (Info.plist) (see here)

  2. Download & install GoogleService-Info.plist with Xcode (see here)

  3. Create APNS key with enabled Push Notifications and uploaded .p8 file to Firebase (see here)

  4. Modify with Swift code:

AppDelegate.swift code:

import UIKit
import Flutter
import Firebase // this is added, and (see further down)

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    FirebaseApp.configure() //add this before the code below
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
  1. Add capabilities to iOS

Info.plist file capabilities (background fetching, notifications, etc)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    </array>
    ...
    <key>NSCameraUsageDescription</key>
    <string>Allows taking a self-portrait profile image or sending a photo while chating</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Allows using your location to make determining country &amp; location picking easier (optional)</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Allows using micrphone to chat with someone when making a call</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>Allows opening files from gallery</string>
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
        <string>bluetooth-central</string>
        <string>external-accessory</string>
        <string>fetch</string>
        <string>location</string>
        <string>processing</string>
        <string>remote-notification</string>
        <string>voip</string>
    </array>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>FirebaseAppDelegateProxyEnabled</key>
    <false/>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
</dict>
</plist>

Note: background modes as shown here cannot be enabled, rather I enabled them using a set of checkboxes to enable Background Fetch, etc.

  1. FCM - Listen and display and notifications/messages using some notification (display) service such as awesome notifications

Implementation follows these guidelines:

Foreground messages:

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('Got a message whilst in the foreground!');
  ...
});

Background messages:

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

  print("Handling a background message: ${message.messageId}");
}

void main() {
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(MyApp());
}

Problem extension:

While debugging, if the iOS app is in foreground/background and a test notification or a valid VoIP call (FCM data message) is sent to the device, the app never receives or (nor does the required breakpoint hit either).

What can cause the app/iOS not to receive these FCM messages?


Sample VoIP message sent from Firebase cloud functions service:

// build notification on liked status
const payload = {
    token: fcmToken,
    data: {
        uid: context.auth.uid,
        room_id: roomId,
        ...
    },
    android: {
        priority: 'high',
        ttl: 0,
        notification: {
            priority: 'max',
            defaultSound: true,
            defaultVibrateTimings: true,
            sticky: true,
            clickAction: "FLUTTER_NOTIFICATION_CLICK",
        }
    },
    notification: {
        title: `Incoming call from ${name}`,
        body: `Accept/Reject call`,
        // icon: 'your-icon-url',                   // Add profile image URL here if necessary
        // clickAction: 'FLUTTER_NOTIFICATION_CLICK' // required only for onResume or onLaunch callbacks
    },
    apns: {
        payload: {
            aps: {
                category: "NEW_MESSAGE_CATEGORY",
                contentAvailable: true,
            }
        }
    },
    headers: {
        "apns-push-type": "background",
        "apns-priority": "5", // Must be `5` when `contentAvailable` is set to true.
        "apns-topic": "io.flutter.plugins.firebase.messaging", // bundle identifier
    },
};

await admin.messaging().send(payload);

Upvotes: 3

Views: 5679

Answers (2)

Ben Butterworth
Ben Butterworth

Reputation: 28532

Original answer (more general/ useful to more people):

FCM does not work on the iOS simulator, so thats half the question solved. For physical devices, take a look at some debugging steps I wrote in detail on a library I work on.

There are multiple ways push notification can go wrong on iOS. When you say: the app never receives, do you mean your Flutter application doesn't get the message? Your device could still be receiving them.

  • Open Console.app on your mac, and start the logging for your physical device. Filter for dasd process, and you can see push notification related logs to see if your device rejected the push notification and did not send it to your app. Most likely, its a problem with how you structured your message.
  • You can also try to debug the didReceiveRemoteNotification method on the iOS side inside the Flutterfire firebase_messaging iOS code. That would mean the message is received by the app.

Let me answer each question/ concern (3) specifically:

  1. My iOS physical device receives push notifications, but only when the app is in the background or closed but NOT when in the foreground.

So you want to show notifications in the foreground? You need to implement userNotificationCenter(_:willPresent:withCompletionHandler:), for example, in Swift:

    public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        if #available(iOS 14.0, *) {
            completionHandler(.banner)
        } else {
            completionHandler(.alert)
        }
    }

To help you a little more here, you need to set your delegate. Place a breakpoint in the method implemented above to see it's actually being called.

  • In Swift: UNUserNotificationCenter.current().delegate = self;
  • In Objective-C: UNUserNotificationCenter.currentNotificationCenter.delegate = self;
  1. Further, it does NOT receive any fcm message (data) notifications at all such as messages with only data and no notification title/body i.e. VoIP status message.

If you do not set the notification field, then you need to set the priority to 5, push type to background, and contentAvailable to true. Unfortunately, you are using the headers field in firebase wrong. The apns object should look like the following, where headers is inside apns, since these are apns specific headers:

    apns: {
        headers: {
            "apns-push-type": "background",
            "apns-priority": "5", // Must be `5` when `contentAvailable` is set to true.
            "apns-topic": "io.flutter.plugins.firebase.messaging", // bundle identifier
        },
        payload: {
            aps: {
                category: "NEW_MESSAGE_CATEGORY",
                contentAvailable: true,
            }
        }
    }

If the message was structured wrongly, you could still get an error on the device, for example, telling you that the priority was 10 (it defaults to 10 if it is not set). Because background messages (no notification) won't be delivered as per the documentation, in Console.app you should see:

CANCELED: com.apple.pushLaunch.com.example.my-example-app-bundle-id:3D8BB6 at priority 10 <private>!

Additionally, the notification’s POST request should contain the apns-push-type header field with a value of background, and the apns-priority field with a value of 5. - Pushing Background Updates to Your App

  1. While debugging, if the iOS app is in foreground/background and a test notification or a valid VoIP call (FCM data message) is sent to the device, the app never receives or (nor does the required breakpoint hit either). What can cause the app/iOS not to receive these FCM messages?

This is the same as #2.

Upvotes: 3

Aystub
Aystub

Reputation: 1642

Uhhh, try doing this in AppDelegate:

override func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
  // ADD THIS LINE
  UNUserNotificationCenter.current().delegate = self
        
  GeneratedPluginRegistrant.register(with: self)
  return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

// ADD THIS FUNCTION
override func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
  // Need to do this or app will eat the notification if app is in foreground
  completionHandler([.alert, .badge, .sound])
}

Edit: Noting that Ben's answer still applies and pushes will not work in the simulator, you'll have to run it on a physical device.

Upvotes: 0

Related Questions