Fix “Received an invalid notification content” in iOS — FCM Flutter
I dont know how long i have been strolling around to solve it, I had tried with the various way by the time, but not with this one, and its worked for me now, at least.
The Main Issue
In my case, i already had log data when FCM payload came :
but when I tried to click the push notification, it prints “Received an invalid notification content”.
A Several Approach Before
UNUserNotificationCenter delegate — I just want to remind you that you need to put :
Objective-C:
if (@available(iOS 10.0, *)) {
[UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}
Swift:
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
if you had solved the problem, you may stop reading.
Then if you received redundant “Receive an invalid notification content” until the app got ERR_BAD_ACCESS (forced close), put this following code inside AppDelegate.swift:
@available(iOS 10, *)
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void
) { completionHandler([[.banner, .sound]])
}@available(iOS 10, *)
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
completionHandler()
}
}
The user notification center will call these methods when notifications arrive or when the user interacts with them. You’ll be working with them a little later.
You may check the detail in FCM guidance in iOS app (https://firebase.google.com/docs/cloud-messaging/ios/client).
if you had solved the problem, you may stop reading.
Payload Content Format
If we read carefully the error message, it seems like we need to pass FCM payload content properly. I already had tried all the github and stackoverflow suggestion at the moment and the problem is not yet solved.
You may check the standard Payload Content Format https://firebase.google.com/docs/cloud-messaging/concept-options#example-notification-message-with-platform-specific-delivery-options
So I assume if you guys had solved the problem by trying all payload format, you may stop read.
Time to solve!
So, the idea was creating `Flutter Native Channel` in `didFinishLaunchingWithOptions` function on AppDelegate.swift
.
Here’s the magic! :
- Put the following code in a state that you want to listen when the Push Notification will be clicked.
Lets say"HomePage"
class _HomeState extends State<Home> with SingleTickerProviderStateMixin { MethodChannel _channel =
const MethodChannel('com.arby.app/backgroundNotifications'); @override
void initState() {
super.initState();
.
.
.
_channel.setMethodCallHandler(setupNotification);
}
Future<dynamic> setupNotification(MethodCall call) async {
print("init state setMethodCallHandler ${call.method}"); // Never comes here
switch (call.method) {
case 'manuallyInterceptNotification':
// variable contains data model obtained from native iOS
// call.arguments would be like {id:1,title:...,body:...,screen:...}
var payload = Map<String,dynamic>.from(call.arguments as Map)
// your navigation code or something goes here...
// Navigate.of(context).pushNamed(payload['screen']);
default:
throw MissingPluginException('notImplemented');
}
}
2. Add FlutterMethodChannel
inside didFinisihLaunch in AppDelegate.swift
...
var manualNotificationChannel : FlutterMethodChannel!override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
....
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
manualNotificationChannel = FlutterMethodChannel.init(name: "com.arby.app/backgroundNotifications", binaryMessenger: controller.binaryMessenger)
....
}
3. Add function sendMessageToFlutter
below didFinishLaunch in AppDelegate.swift
, it will fires state in home.dart when this function get invoked
private func sendMessageToFlutter(result : Dictionary <String, String>){
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
self.manualNotificationChannel.invokeMethod("manuallyInterceptNotification", arguments: result)
}
}
4. Add overriding function based on FCM documentation (https://firebase.google.com/docs/cloud-messaging/ios/client), inside the override function, we’ll put the following codes :
@available(iOS 10, *)
override func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
Messaging.messaging().appDidReceiveMessage(userInfo)
var payload: [String: String] = [:]
// this is the example of key:value data inside your payload
if let id = userInfo["id"] {
payload["id"] = "\(id)"
}
if let body = userInfo["body"] {
payload["body"] = "\(body)"
}
if let title = userInfo["title"] {
payload["title"] = "\(title)"
}
if let screen= userInfo["screen"] {
payload["screen"] = "\(screen)"
}
sendMessageToFlutter(result:payload);
completionHandler()
}
The payload supposed to be like this :
curl -X POST \
https://fcm.googleapis.com/fcm/send \
-H 'Authorization: key=your_server_key' \
-H 'Content-Type: application/json' \
-H 'cache-control: no-cache' \
-d '{
"content_available": true,
"mutable_content": true,
"priority":"high",
"data":
{
"id":"8",
"body":"This is the body",
"title":"Hello Flutter"
"screen":"/detail"
},
"notification":
{
"title": "Title",
"body" : "Test",
"content_available": 1
},
"to": "your_token"
}'
I didnt know whats the best practice to convert AnyHashable
to Dictionary
(we are using Dictionary
due to dart received data-type rules https://docs.flutter.dev/development/platform-integration/platform-channels?tab=type-mappings-swift-tab#codec)
Yet, we still waiting for the Firebase (i thought) to solve the problem.