Not getting payload data when app is terminated, Flutter FCM and LPN - firebase

I'm setting up push notifications via FCM and LocalPushNotifications, I was able to set it up in foreground state of app and in the background. In terminated state of the app, I do receive the notification, but when I press on it, no action happens, even though the foreground and background state of the app is working fine and navigates the user to the notification screen, in the terminated state, the app just opens and it doesn't navigate to the notification screen, only opens the main screen of the app. Since the device is not connected I can't see the error inside the console log, but when I start the app from the emulator, this is what I get on start:
I/flutter ( 3829): Got a message whilst in the terminated state!
I/flutter ( 3829): Message data: null
This is called inside the pushNotifications() method at FirebaseMessaging.instance.getInitialMessage().then()...
Here is the code with comments inside:
Logic for handling push notifications:
Future<void> pushNotifications() async {
await Firebase.initializeApp();
RemoteMessage initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
_handleMessage(initialMessage);
}
/// THIS IS NOT WORKING, IT OPENS THE APP BUT DOESN'T NAVIGATE TO THE DESIRED SCREEN
///gives you the message on which user taps
///and it opened the app from terminated state
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
LocalNotificationService.display(message);
print('Got a message whilst in the terminated state!');
print('Message data: ${message.data}');
if (message != null) {
print('terminated state');
}
});
///forground work
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
LocalNotificationService.display(message);
print('Got a message whilst in the foreground!');
print('Message data: ${message.data}');
});
///EVEN THOUGH IT IS SUPPOSED TO WORK LIKE THIS, I ONLY MANAGED TO MAKE IT WORK WITH BACKGROUND HANDLER, THIS METHOD NEVER TRIGGERS
///When the app is in background but opened and user taps
///on the notification
FirebaseMessaging.onMessageOpenedApp.listen((message) {
print('Got a message whilst in the background!');
print('Message data: ${message.data}');
_handleMessage(message);
LocalNotificationService.display(message);
});
}
///THIS HANDLES THE NOTIFICATIONS WHEN THE APP IS IN THE BACKGROUND
Future<void> _handleMessage(RemoteMessage message) async {
await Firebase.initializeApp();
if (message.data != null) {
print('message handler');
LocalNotificationService.display(message);/// ALL OF THESE CALLED FROM THE LocalNotificationService CLASS BELOW
}
}
///MAIN METHOD, WHERE I INITIALIZE FIREBASE AND THE METHODES ABOVE(pushNotifications()), HANDLE THE MESSAGES WITH onBackgroundMessage(_handleMessage),
void main() async {
WidgetsFlutterBinding.ensureInitialized();
pushNotifications();
var initializationSettingsAndroid =
AndroidInitializationSettings("#mipmap/ic_launcher");
var initializationSettingsIOS = IOSInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
await Firebase.initializeApp();
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
FirebaseMessaging.onBackgroundMessage(_handleMessage);
runApp(MyApp());
}
My local notifications service class:
class LocalNotificationService {
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
static void initialize(BuildContext context) {
final InitializationSettings initializationSettings =
InitializationSettings(
android: AndroidInitializationSettings("#mipmap/ic_launcher"));
_notificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String payloadData) async {
if (payloadData!= null) {
Navigator.pushNamed(context, NotificationsScreen.id);
}
});
}
static void display(RemoteMessage message) async {
try {
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
"push notifications",
"push notifications",
"push notifications",
importance: Importance.max,
priority: Priority.high,
));
await _notificationsPlugin.show(
id,
'push notifications',
'You have received a new push notification!',
notificationDetails,
payload: message.data['default'], // THIS IS NULL WHEN IN TERMINATED STATE OF APP
);
} on Exception catch (e) {
print('exception: ' + e.toString());
}
}
}
So like I said, both foreground and background state is working and corresponding to the correct screen, but the terminated app state is not corresponding at all, but it does show the notification and opens the app when tapped on it.
Am I missing something? I mostly followed the documentation and some stuff on my own, but it is still not working as desired.
Any form of help is appreciated, thanks in advance!

I haven't been able to completely crack the problem you describe, and its very frustrating.
This is what I've figured out so far, and I'll put this here as it may help you:
There are 3 types of messages, data, notification and mixed.
If you use a pure data payload
The message is essentially silent.
onMessage does respond to data notifications when app foregrounded
onMessageOpenedApp does not trigger at all for data messages in any scenario
onBackgroundMessage is used to show a message while in terminated state or background state using local notifications
It is up to you (using the local notifications package) to deal with their clicks, using the onSelectNotification
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: selectNotification
);
You cannot use onSelectNotification in all these scenarios (as far as I've been able to yet determine *** this is a stumbling block for me right now
If you use a pure notification or mixed payload
The FCM package will display notifications for you when the app is backgrounded (but not foreground)
onMessage does respond to data when app foregrounded
onMessageOpenedApp WILL trigger when the notification is clicked in background state (but not for terminated state)
onBackgroundMessage will trigger, but unlike above, you should not use this to display a notification, as that will force a double notification (one processed by the FCM package and the other done by you manually)
Clicks, are dealt with by the package when the app is backgrounded, but by you when foregrounded. *** possibly also terminated, not sure yet.
As I mentioned, I've laid down some of the facts as Ive figure them out so far. Its very tricky and frustrating. Whats slowing me down immensely is that when using a mixed payload (what I've been using), while terminated the notifications either don't come at all, or come at their own pace (hours after they are sent).
If you make progress on this problem, let me know I think we are in the same boat...

#GrandMagus As luck has it I just got mine to work!! (getting the onMessageOpenedApp to trigger So I made a few changes and I'm not sure which one got it going.
I cleared all my caches and deleted the app and installed from scratch.
Edited AndroidManifest.xml to make changes as suggested by the documentation, in case you did do that they are:
<intent-filter> <action android:name="FLUTTER_NOTIFICATION_CLICK" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="high_importance_channel" />
Changed my payload to be mixed data and notification. I included FLUTTER_NOTIFICATION_CLICK in my payload. I can explain that one further incase you don't know what that is. Its key to make your payload have at least the notification information. This forces the package to create the notification which will then respond to clicks. Do not handle creating the local push notification yourself in this scenario or you will get 2 notifications. If you are getting two, you will notice that the one generated by the package will in fact run the onMessageOpenedApp
I started testing my app in Release mode. Im wondering if the AndroidManifest.xml changes that allow for the heads up notifications only work if they are placed in each .xml file. In this case, I was editing the release file but testing on the debug version

Related

How to update the UI with FCM background notifications?

I want to navigate to some page when user taps on the notification. I'm using cloud messaging and flutter_local_notifications. I've managed to the do it with foreground notifications. It was pretty straightforward. I pasted the same code to my background notification handler, didn't work. Also I've look for onTap callback for notifications but couldn't find anything related to that.
Here's my background notification handler.
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
var androidDetails = AndroidNotificationDetails(
"Channel ID",
"Shannonside",
"Shannonside Channel",
);
var iosDetails = IOSNotificationDetails();
var details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
if (message.notification != null) {
final title = message.notification.title;
final body = message.notification.body;
await NotificationService.localNotification.show(0, title, body, details);
}
if (message.data != null) {
var articleId = message.data['articleId'];
var category = message.data['category'];
if (articleId != null && category != null) {
print("ArticleID: $articleId Category $category");
//#TODO Add navigation service and move to the article detail
NavigatorService.instance.navigateTo("/articlePage", arguments: {
"articleId": articleId.toString().toLowerCase(),
"category": category.toString().toLowerCase(),
});
}
}
}
It's not working, not even my function fired up. Also they stated in the documentation that it's not possible.
Since the handler runs in its own isolate outside your applications context, it is not possible to update application state or execute any UI impacting logic. You can however perform logic such as HTTP requests, IO operations (updating local storage), communicate with other plugins etc.
I know some apps do that, they open some page when you click on notification. Like a dynamic link. I want to implement this in my app.
You can use function
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
print("FirebaseMessaging.getInitialMessage");
if (message != null) {
Navigator.of(context).pushNamed('/call');
}
});
This function only run the first time when the app open, It gets last message

Firebase FCM onLaunch code not running functions when clicking on notification

I am using FCM to deliver notifications that once clicked take the user to a specific page on the app. Currently, the onMessage and onResume functions work perfectly fine but on launch does not.
I include the
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
and
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
because I am also using flutter local notifications.
I have already tried removing these lines but they do not make a difference.
Ok so the problem I had was that when the on launch code fires my app was loading other data and functions e.t.c when on the loading splash page. What I had to do was when on launch fires I saved the notification in a variable. Then I executed the code I wanted after the splash init was finished
onLaunch: (Map<String, dynamic> notification) async {
print("onLaunch: $notification");
///Saving the notification to use once the rest of the initialising and
///Loading is done
launchNotification = notification;
},
Then once the loading and the initialising of other processes had finished I ran the ran this function
onLaunchFunction() async {
await Future.delayed(Duration(milliseconds: 100));
Map tempNotification = launchNotification;
if (launchNotification != null) {
launchNotification = null;
///The rest of the function
}
}
I added an a future delayed just to make sure that my code would run whilst the initialising. This may not be needed

Firebase FCM notification received in iOS simulator but as GCM(?) on real iOS device in flutter app

edit: it is supposed to look like it does on the device log, according to Firebase support
I am adding push notifications via FCM to my Flutter app, but the message format is very different on the iOS Simulator vs. my iPhone 5s.
When receiving a push notification from the Firebase console to an active/opened app.
Problem: What do I need to do to make sure the real device receives the message in the correct format?
Log from Simulator (iPhone XR, 12.2) (looks like in the official code examples):
onMessage: {
from: 123000000000,
collapse_key: com.mydomainnamehere,
notification: {
body: Lorem ipsum,
title: Title,
e: 1,
tag: campaign_collapse_key_9876543210011223344
}
}
Log from real device (iPhone 5s, 12.2) (can't find any references online to this):
onMessage: {
google.c.a.c_l: notif_name,
google.c.a.e: 1,
aps: {
alert: {
title: Title,
body: Lorem ipsum
}
},
gcm.n.e: 1,
google.c.a.c_id: 9876543210011223344,
google.c.a.udt: 0,
gcm.message_id: 1234567800998877,
google.c.a.ts: 1234567800
}
The notification is sent from the Firebase console to all devices, the logs are taken from the same notification (but I anonymized the id's).
The Device and Simulator is running the same Flutter code from Android Studio, at the same time.
Parts of my pubspec.yaml that refers to FCM
firebase_core: ^0.4.0+1
firebase_auth: 0.11.1
cloud_firestore: ^0.11.0+2
firestore_ui: ^1.4.0
firebase_messaging: ^5.0.2
Software and SDK Versions
Flutter Channel dev, v1.8.4,
Mac OS X 10.14.5,
Android SDK version 28.0.3,
Xcode 10.2.1,
Android Studio version 3.4
Flutter message-handling code
void initState() {
super.initState();
if (Platform.isIOS) {
iosSubscription = _fcm.onIosSettingsRegistered.listen((IosNotificationSettings settings) {
print("FCM settings received: $settings");
});
_fcm.requestNotificationPermissions(IosNotificationSettings());
}
_fcm.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
},
);
getFcmToken();
}
void getFcmToken() async {
var token = await FirebaseMessaging().getToken();
print("Token $token");
}
I was expecting that the JSON format would be the same on both the simulator and a real device. But the real device isn't even receiving all of the notification.
According to Firebase support, we should not be able to get push notifications in the simulator, and they say that the gcm-format above is indeed the correct one.
The solution is to always use key-value pairs as stated in the answer to this previous question FCM - Get Message Label
For those who still struggle with this, there seems an alternative solution.
While the structure of the data can be different in various conditions (iOS/Android or Real device/Simulator), the key names of those essential parts of the data are considered to be unique no matter how deeply nested: "title" and "body". Therefore, extracting the values of those 'title' and 'body' entries might solve the problem.
//get title value from the message load (whether data or push)
String _title = _findFirstKeyValue(message, 'title');
The following is a recursive function to get the first matching entry from the message Map.
String _findFirstKeyValue(Map data, String targetKey) {
for (final k in data.keys) {
final v = data[k];
if (v is Map) { // go deeper if the value is still a kind of Map
final String _temp = _findFirstKeyValue(v, targetKey);
if (_temp != '') return _temp;
} else { // when the value is primitive Key-Value pair
if (k.toString().toLowerCase() == targetKey.toLowerCase()) {
return v.toString();
}
}
}
return '';
}
Note that return will not be fired if you use data.forEach rather than ordinary for loop.

Parse Server Cloud code keeps sending push notifications

I've developed an app that sends push notifications using Parse Server Cloud code. These notifications are received correctly in the devices but hours later they are automatically sent from Parse Server again (and they are received again). This happens 3 or 4 times for each push notifications.
If push notifications are sent from Parse Dashboard they are only sent once, so it seems it's a problem of my cloud code.
This is my code:
Parse.Cloud.define("sendPushNotification", function(request, response) {
var userId = request.params.userId;
var message = request.params.message;
var queryUser = new Parse.Query(Parse.User);
queryUser.equalTo('objectId', userId);
var query = new Parse.Query(Parse.Installation);
query.matchesQuery('user', queryUser);
Parse.Push.send({
where: query,
data: {
alert: message,
badge: 0,
sound: 'default'
}
}, {
success: function() {
console.log('##### PUSH OK');
response.success();
},
error: function(error) {
console.log('##### PUSH ERROR');
response.error('ERROR');
},
useMasterKey: true
});
});
I had a similar issue sending emails from another cloud code function (not included in the question) and my problem was because I forgot to add response.success(); and response.error('ERROR'); methods.
So this time I was sure to include these 2 calls in the responses of "sendPushNotification" method.
After sending a push notification the logs show this:
2017-07-09T15:38:02.427Z - Ran cloud function sendPushNotification for user undefined with:
Input: {"message":"This is my message","userId":"myUserIdInParse"}
Result: undefined
I think that this "Result: undefined" could be related with the problem because success and error functions are not called.
What could be the problem with this code? Why the code doesn't receive a success() when the notifications are received correctly in the devices?

Increment app badge on recieving ACS push while app on background: Titanium

My application uses ACS Push Notification. I have implemented app badge in my application. But the problem is the appBadge doesn't incrementing automatically while receiving a push notification. I have used the following code in my app
var deviceToken;
Titanium.Network.registerForPushNotifications({
types: [
Titanium.Network.NOTIFICATION_TYPE_BADGE,
Titanium.Network.NOTIFICATION_TYPE_ALERT,
Titanium.Network.NOTIFICATION_TYPE_SOUND
],
success:function(e)
{
deviceToken = e.deviceToken;
SubscribeToPush(channelName, deviceToken, type);
},
error:function(e)
{
alert("Error: "+ ((e.error && e.message) || JSON.stringify(e.error)));
},
callback:function(e)
{
var badgeCount = Ti.UI.iPhone.getAppBadge();
badgeCount = badgeCount + 1;
Ti.UI.iPhone.setAppBadge(badgeCount);
}
});
I read here that "callback function" invoked upon receiving a new push notification. So I set the following code as callback to increment the badge.
callback:function(e)
{
var badgeCount = Ti.UI.iPhone.getAppBadge(); //Will return the app badges
badgeCount = badgeCount + 1; //Incrementing the appbadge
Ti.UI.iPhone.setAppBadge(badgeCount); //Setting new appbadge
}
It works while the app is open and when it receives a notification, callback get fired and when the app go to background, the badge get appeared. But I want to increment the badge number when the app is in background or exited. Can anyone help me to resolve this issue?
After lots of research I have created a sample application to increment the appBadge while receiving a server push notification. You can download the code from Increment the ios appBadge Titanium. Please follow the steps after downloading the resources folder.
Create a new mobile application project in Titanium.
Replace the resources folder with the one you downloaded.
Login to www.appcelerator.com, go to your app then go to Manage ACS
Create a new user as admin, set user as admin
Create a new Access Control List(ACS) using the admin user and give the ACL Name as 'SampleApp'
Upload the p12 certificate for push notification
Now Install the application to your iPhone and run the app...
Each user of the app should have a custom object which stores the number of notifications. I'm updating them while sending a push and clears it while I resume/open the application. I tested it with my iPhone devices and it works perfect. However it takes some delays since I have to call ACS multiple times.
UPDATE : Latest Titanium SDKs Support this feature by default.
What you need to do is to change the payload as follows:
var payload = {"alert":"Hi, This is a test notification", badge: "+1"};
/*+1 will increment the current appbadge by 1, number of appbadge will be saved in the ACS*/
Cloud.PushNotifications.notify({
channel: 'friend_request',
payload: payload
}, function (e) {
if (e.success) {
alert('Success');
} else {
alert('Error:\n' +
((e.error && e.message) || JSON.stringify(e)));
}
});
And this will increase the appbadge by one. And you need to reset the appbadge when you resume/open of your application as follows
Cloud.PushNotifications.resetBadge({
device_token : myDeviceToken
}, function(e){
if(e.success){
Ti.UI.iPhone.setAppBadge(0);
} else {
//Error callback
}
})

Resources