How to show only one notification with FCM and awesome_notifications - flutter

I am using Firebase Messaging to send users notifications from the cloud, but I want some notifications to have action buttons options (text input, button selections) from within the notification, so I'm using the awesome_notifications.
I have set up the package to run this method whenever an notification is received:
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: 1,
channelKey: 'basic_channel',
title: message.notification.title,
body: message.notification.body,
createdSource: NotificationSource.Firebase,
notificationLayout: NotificationLayout.BigText,
),
actionButtons: <NotificationActionButton>[
NotificationActionButton(
key: 'short_text',
label: 'Answer',
buttonType: ActionButtonType.InputField,
autoCancel: true,
),
],
);
}
This does properly generate a notification with the correct title, body, layout, and text input option, but the notification received directly from FCM is also displayed. I only want the notification that I generate with awesome_notifications to be shown but I can't seem to find a way to do this. Thanks for any input.

Firebase Cloud Messaging(FCM) has two types of messages:
Notification messages in which FCM automatically displays the message to end-user devices on behalf of the client app.
Data messages in which your app is responsible for processing data messages.
About FCM messages
You should use a data message so your app does not display the automatic notification that comes with the notification message type that you currently use.
So if your cloud function contained this code below to send notification messages:
const message = {
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"notification":{
"title":"Portugal vs. Denmark",
"body":"great match!"
}
}
};
admin.messaging().send(message);
you can update it to:
const message ={
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"data":{
"title" : "Portugal vs. Denmark",
"body" : "great match!"
}
}
};
admin.messaging().send(message);

Related

How to handle notification action button clicks in background in Flutter using awesome_notifications?

I am using awesome notifications in Flutter to show local notifications. My plan is to have an action button in a notification, which changes some values in the shared preferences in the background (without opening the app) when pressed. Is this even possible to do?
I tried using onActionReceivedMethod-listener and getInitialNotificationAction-method in my main function after initializing awesome notifications:
AwesomeNotifications().setListeners(onActionReceivedMethod: (action) async{
print(action.body);
});
// OR
ReceivedAction? receivedAction = await AwesomeNotifications().getInitialNotificationAction(
removeFromActionEvents: false
);
if (receivedAction?.body != null){
print(receivedAction.body);
}
Both of them worked (separetly used) only when the app had already started, but they also gave this error:
Awesome Notifications: A background message could not be handled in Dart because there is no dart background handler registered. (BackgroundService:58)
But how I could get it working when the app is not opened? Can I create a backgroung handler without Firebase, or is there some other way to achieve this?
Here is my code, how I create the notification:
static Future<void> createSimpleNotification() async {
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: 1,
channelKey: 'important_channel',
title: 'Title',
body: 'Test',
largeIcon: 'asset://assets/iconPhoto.png'),
actionButtons: [
NotificationActionButton(
key:'key',
label: 'label',
actionType: ActionType.SilentBackgroundAction)]
);
}
I found the answer from here: Cannot find AwesomeNotifications().actionStream
I implemented it like this:
#pragma("vm:entry-point")
Future<void> _onActionReceivedMethod(ReceivedAction action) async {
print('It works');
}
And it worked both when the app was in the background or in the foreground.

Flutter FCM shows empty notification when no notification data is sent and when app is in bg

I'm sending FCM notification from my cloud functions to my flutter app.
I don't want the FCM system to handle notification display when the app is in background or terminated. Hence, I'm sending the data to show in notification in the data key instead of the notification key.
Cloud Functions:
await admin.messaging().sendMulticast({
tokens: userFCMTokens,
data: {
title: "Notification title to display",
body: "Notification body to display",
},
android: {
notification: {
channelId: "some_channel_id",
},
},
});
On the client side (i.e. Flutter app) I'm receiving/handling the FCM messages and displaying them with the awesome_notifications plugin.
Flutter:
AwesomeNotifications().createNotification(
content: NotificationContent(
id: message.hashCode,
channelKey: message.notification?.android?.channelId,
title: message.data['title'],
body: message.data['body'],
bigPicture: message.data['imageUrl'],
wakeUpScreen: true,
notificationLayout: NotificationLayout.BigPicture,
),
actionButtons: (message.data['actions'] as List<Map>?)
?.map(
(e) => NotificationActionButton(
key: e['key'],
label: e['label'],
),
)
.toList(),
);
This setup works properly when the app is in foreground but when in background or terminated it shows 2 notification. One proper and one totally empty notification.
I don't want the FCM SDK system to handle the notification hence I'm using data instead of notification but then this problem arises.

Firebase FCM background notifications click_action

i want to navigate to a specific screen route when i click on the background notification
for now the default behavior is just launching my app, so how and where do i change the default behavior of the click action
i'm sending the notification using cloud functions, here is the code
const payload: admin.messaging.MessagingPayload = {
notification: {
title: doc["senderName"],
body: doc["msg"],
sound: "default",
badge: "1",
},
data: {
type: "chat",
},
};
return fcm
.sendToDevice(tokens, payload);
somehow i want to access the type "chat" so from there i can navigate to a chat screen
Use onMessageOpenedApp to handle when a user presses a notification. RemoteMessage has data property which holds the custom parameters.
When you click on the notification from background state, onMessageOpened stream function is called.
Initialize this stream function in the initState of first page of the app and check the data in payload you received from the notification just like this:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
if (message != null) {
String screenName = message.data['screenName'];
print("Screen name is: $screenName");
if (screenName == 'chat') {
//Navigate to your chat screen here
}
}
});

Flutter display a notification for a long time like whatsapp call

When i receive a call notification on whatsapp it stays on a screen for a long time.
I am trying to create same behaviour using flutter and FCM notification. I am using below code
I am using same collapseKey but after showing notification two times the notification appear in background and not as heads-up notification.
callNotificationTimer = Timer(Duration(seconds: 8), () {
sendPayLoad(
fcmToken,
collapseKey: collapseKey,
callData: request,
userData: userData,
);
});
sendPayLoad(
fcmToken,
collapseKey: collapseKey,
callData: request,
userData: userData,
);
I highly recommend you to use this package: flutter_incoming_call to display a notification for a long time like whatsapp call

How to disable cloud messaging per device/user in flutter?

For a flutter app I’m using Firebase Cloud Messaging and cloud functions to send push notifications to users, using their FCM registration tokens. The app has a settings page where users should be able to turn off certain push notifications. The notifications are user specific, so a topic to subscribe or unsubscribe to wouldn’t work, but the notifications can be classified in certain categories.
For example in a chat app when user A send a message to user B that push notification could be in a category of ‘chat messages’, while user A could also delete the chat with user B and that push notification could be in a category of ‘deleted chats’.
How can I make it so that user B can turn off notifications for ‘deleted chats’, while still receiving notifications for ‘chat messages’? Is it possible to use a condition with a topic and a user’s registration token on one way or the other? Any ideas are greatly appreciated!
Thanks to a big nudge in the right direction from Doug, I was able to figure it out! Posting my code below to help anyone take the same step in the right direction.
So, in my flutter app' settings page the user can turn notifications on and off for a few categories. The user's preference is then stored in a user specific document in my Cloud Firestore users collection. See the below code for an example of the SwitchListTile I used on the settings page.
SwitchListTile(
title: Text('Admin notifications'),
subtitle: Text('Maintenance and general notes'),
onChanged: (value) {
setState(() {
adminNotifications = value;
Firestore.instance
.collection('users')
.document(loggedInUser.uid)
.updateData({
'adminNotifications': value,
});
});
save('adminNotifications', value);
},
value: adminNotifications,
),
In my cloud function I added a reference to the document in the users collection and a check to see if the value of the field adminNotifications is equal to true. If so, a notification is send, otherwise a notification is not send to the user. Below I've added the cloud function. Please do note that the cloud function renders 'nested promises' warnings, but it works for now! I'm still a Flutter beginner so I was pretty happy to get it working. Big thanks again to Doug!
exports.userNotifications = functions.firestore.document('notifications/{any}').onCreate((change, context) => {
const userFcm = change.data().fcmToken;
const title = change.data().title;
const body = change.data().body;
const forUser = change.data().for;
const notificationContent = {
notification: {
title: title,
body: body,
badge: '1',
click_action: 'FLUTTER_NOTIFICATION_CLICK',
}
};
var usersRef = db.collection('users');
var queryRef = usersRef.where('login', '==', forUser).limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
const adminNotifications = doc.data().adminNotifications;
console.log(adminNotifications);
if(swapNotifications === true){
return admin.messaging().sendToDevice(userFcm, notificationContent)
.then(() => {
console.log('notification sent')
return
})
.catch(error =>{
console.log('error in sending notification', error)
})
} else {
console.log('message not send due to swapNotification preferences')
}
return console.log('reading user data success');
})
.catch(err => {
console.log('error in retrieving user data:', err)
})
});