I'm developing a Flutter app (using Firebase Firestore and FCM) that aims to send notifications to all devices where the user has signed in. When the user signs out from a device, the token will be removed from Firebase. I am looking for a solution to remove the token when user uninstall the app, and in a way, able to store/update the multiple device tokens for a single user without storing the token based on the android id.
What I am using for sending notification:
final FirebaseMessaging firebaseMessaging = FirebaseMessaging();
Future<Map<String, dynamic>> sendAndRetrieveMessage(context) async {
await firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true, provisional: false),
);
await http.post(
'https://fcm.googleapis.com/fcm/send',
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'key=$server_token',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': '${widget.content}',
'title': '${widget.title}',
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'status': 'done',
'body': '${widget.content}',
'title': '${widget.title}',
},
'registration_ids': user_tokens,
},
),
).then((http.Response response){
final int statusCode = response.statusCode;
print("RESPONSE BODY: " + response.body);
print("STATUS CODE: " + statusCode.toString());
if (statusCode < 200 || statusCode > 400 || response.body == null) {
throw new Exception("Error while fetching data");
}
});
final Completer<Map<String, dynamic>> completer =
Completer<Map<String, dynamic>>();
firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
completer.complete(message);
},
onLaunch: (Map<String, dynamic> message) async {
completer.complete(message);
},
onResume: (Map<String, dynamic> message) async {
completer.complete(message);
},
);
return completer.future;
}
I have tried these methods for my notification module:
Update signed-in token in Firebase, but updating only one string also means only sending to the latest signed-in device of the user ignoring the other devices
Subscribe user to user_id topic, but topics are limited to 5 per sending notification. My app needs to send to a list of specific users (undetermined by a common topic, but a set of multiple conditions) and I want to avoid using loops as the notification function needs to work simultaneously for all receiving users
Read HTTP response message to detect failed token, the response returns "NotRegistered" but the response does not return the specific failed token
I would like to know if this is doable in Flutter and if there are solutions/simpler methods for this problem. Thank you
Related
Future<Map<String, dynamic>> sendAndRetrieveMessage({String thatToken, String bodyMessage, String titleMessage}) async {
print('\n$thatToken' + '\n$bodyMessage' + '\n$bodyMessage');
await firebaseMessaging.requestPermission(
sound: true,
badge: true,
alert: true,
provisional: false,
);
await http.post(
'https://fcm.googleapis.com/fcm/send',
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'key=$serverToken',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{'body': 'this is a body', 'title': 'this is a title'},
'priority': 'high',
'data': <String, dynamic>{'click_action': 'FLUTTER_NOTIFICATION_CLICK', 'id': '1', 'status': 'done'},
'to': thatToken,
},
),
);
final Completer<Map<String, dynamic>> completer = Completer<Map<String, dynamic>>();
FirebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
completer.complete(message);
},
);
return completer.future;
}
Hello World! Code above is firebaseMessaging configuration code that I used before, and today, I'm finally trying migration to latest version, which is 8.0.0-dev.14 firebaseMessaging. And here I got tough issue to solve. Migration documentation says that '.configure()' is totally deleted and I have to use custom static methods for each cases. So, here comes the problem.
I could following substitution guides for other parts of migration from source code, but I cannot find out which exact code snippet is needed to perform same feature with code above. This is because I got only few ideas what those Completer or configuration are exactly doing. My fault.. ]:
So, I hope someone who have already got through these migration issues, especially the 'configure' method help me out. Lastly, code below is culprit with its error message. Thank you in advance [:
FirebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
completer.complete(message);
},
);
errorMessage for above snippet: error: The method 'configure' isn't defined for the type 'FirebaseMessaging'.
To migrate to Firebase messaging > 8.0.0 you can use initializeApp from Firebase core then add callbacks to the onMessage method of FirebaseMessaging
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
await Firebase.initializeApp();
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
completer.complete(message);
});
Source: https://firebase.flutter.dev/docs/migration/#plugin-changes
Currently I have topics to send push notifications. In particular
all: this is a topic in which I send notifications to everyone
paid: this is a topic in which I send notification only to a small group of users
Today I was upgrading Flutter to the newest firebase package and I encountered this:
<String, dynamic>{
'notification': <String, dynamic>{
'body': 'this is a body',
'title': 'this is a title'
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done'
},
'to': await firebaseMessaging.getToken(),
},
But wait, isn't 'to' meant to send notifications only to a specific device? Why does
'to': await firebaseMessaging.getToken(),
this send a message to all devices? I am confused because the doc says that 'to' is for specific targets too. Thanks!
The to property in an FCM message determines where it is sent to. The value of the to property can be the device token of a single device, it can be the ID of a device group, or it can be a topic.
It sounds like in your actual code you pass a topic in to, while in the sample in your question it passes a device token.
you can use this Firestore api by google to send push notifications
Future<bool> callOnFcmApiSendPushNotifications(List <String> userToken) async
{
final postUrl = 'https://fcm.googleapis.com/fcm/send';
final data = {
"registration_ids" : userToken,
"collapse_key" : "type_a",
"notification" : {
"title": 'NewTextTitle',
"body" : 'NewTextBody',
}
};
final headers = {
'content-type': 'application/json',
'Authorization': constant.firebaseTokenAPIFCM // 'key=YOUR_SERVER_KEY'
};
final response = await http.post(postUrl,
body: json.encode(data),
encoding: Encoding.getByName('utf-8'),
headers: headers);
if (response.statusCode == 200) {
// on success do sth
print('test ok push CFM');
return true;
} else {
print(' CFM error');
// on failure do sth
return false;
}
}
I try to implement group notification like this android group notification and ios group notification. But I can't able to do it. I tried this flutter_local_notification plugin too. but this works only when app open. not working on foreground(onResume) and background.
void registerNotification() {
_fcm.configure(
onMessage: (Map<String, dynamic> message) {
return;
},
onResume: (Map<String, dynamic> message) {
return;
},
onLaunch: (Map<String, dynamic> message) {
return;
},
onBackgroundMessage: backgroundMessageHandler);
}
payload
const payload = {
notification: {
title: title,
body: message,
},
data: {
click_action: "FLUTTER_NOTIFICATION_CLICK",
sound: "default"
},
android: {
priority: "high",
collapse_key: userName,//tried to add collapse_key for group notification
},
apns: {
headers: {
"apns-priority": "5",
},
},
token:token,
};
SOLUTION
I saw the react-native answer for this, you have to do same thing using Flutter with firebase_messaging react native answer
If you want to submit a group, you need to subscribe to a topic, in the documentation that mentions it, you would no longer send a single token, but would instead subscribe to a topic and submit it to that topic.
Below is the notification payload in cloud function for FCM notification in flutter application, I am not able figure out how to access the elements of the data:{} when the notification is received in flutter application
PAYLOAD
const payload = {
notification: {
title: `NOTIFICATION2 `,
body: contentMessage,
badge: '1',
sound: 'default'
},
data: {
click_action: 'FLUTTER_NOTIFICATION_CLICK',
notification2: notificationid2,
detail: detail,
senderAvatarURL: messageRecieverSenderAvatar,
category: 'default'
}
}
NOTIFICATION CODE
firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) {
print('onMessage: $message');
Platform.isAndroid ? showNotification(message['notification']) : showNotification(message['aps']['alert']);
return;
}, onResume: (Map<String, dynamic> message) {
print('onResume: $message');
return;
}, onLaunch: (Map<String, dynamic> message) {
print('onLaunch: $message');
return;
});
you can use the below code to access data :
if (message.containsKey('data')) {
// Handle data message
final dynamic data = message['data'];
}
once you got the data map, you can parse it.
the package https://pub.dev/packages/firebase_messaging shows an example of handling data messages, check part with title
Define a TOP-LEVEL or STATIC function to handle background messages
Goal: send notification for all user devices.
Problem description: I've problem with sending notification for one user. After registration, I store FCM token in database for user to send notification for him. Problem is that when user will register multiple times - same token will be stored for more than one user.
Example:
user register with emails test#gmail.com and test2#gmail.com
user login to test#gmail.com
other user send notification to test#gmail.com
notification will be send on both accounts (test#gmail.com and test2#gmail.com) instead of only to test#gmail.com
So here's my question - how to send notification to all devices connected to user instead of all users connected with device?
QuerySnapshot ref = await Firestore.instance.collection('users')
.document(_textController.text)
.collection('tokens')
.getDocuments();
ref.documents.forEach((snapshot) async {
http.Response response = await http.post(
'https://fcm.googleapis.com/fcm/send',
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'key=$serverKey',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': 'this is a body',
'title': 'this is a title'
},
'priority': 'high',
'data': <String, dynamic>{
'click_action':
'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done'
},
'to': snapshot.data['token'],
},
),
);
});
Thanks for help!
I FOUND SOLUTION!
All you have to do if you have the same problem is deleting token on logout.
final Firestore store = Firestore.instance;
final FirebaseAuth auth = FirebaseAuth.instance
String token = await _fcm.getToken();
await store
<reference to token document>
.delete();
await auth.signOut();