我正在尝试为聊天应用实施FCM通知,我想实现" WhatsApp-like"通知将通过对话分组的通知。
当Alice回复与Bob内部的对话时,Bob会收到一个通知,主体是" Alice:内容"。但如果Alice再次快速回复对话,Bob会收到另一个新通知,但我只是想将新回复附加到prev通知上。
我怎样才能做到这一点?
答案 0 :(得分:2)
通过分组通知,我假设您的意思是堆叠或bundling notifications。
这更多地是关于如何在客户端应用中处理通知。您只需使用setGroup()将所有通知添加到单个组,然后调用notify()让NotificationManager进行更改。
这个Add Each Notification to a Group文档几乎总结了这一切。
答案 1 :(得分:1)
您可以使用'标记'在通知有效载荷中:
tag可选,字符串
用于替换通知抽屉中现有通知的标识符。 如果未指定,则每个请求都会创建一个新通知。 如果已指定且已显示具有相同标记的通知,则新通知将替换通知抽屉中的现有通知。
来源:https://firebase.google.com/docs/cloud-messaging/xmpp-server-ref#notification-payload-support
答案 2 :(得分:0)
您可以使用以下代码对通知进行分组。但是我没有实现回复按钮,因为我使用的是Android Web视图应用程序。 我已经基于Android版本使用了两种类型的通知分组,因为android nougat和更高版本会根据组ID自动将通知分组,但是棉花糖和更早的版本不会将通知分组。
我的Firebase邮件处理服务如下:
import static com.packageName.config.AppConstant.MY_NOTIFICATION;
public class MyFireBaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFireBaseService";
private static final int SUMMARY_ID = 999;
@Override
public void onNewToken(String refreshedToken) {
super.onNewToken(refreshedToken);
//Store fcm token to shared preferences
SharedPrefManager.getInstance(getApplicationContext()).setFCMToken(refreshedToken);
}
@Override
public void onCreate() {
super.onCreate();
}
/* Data messages should be in the form of
* {
* type(Required) : "NotificationDTO type"
* title(Required) : "NotificationDTO title"
* message(Required) : "Message to be displayed in the notification panel"
* notificationURL(Required) : "Url to be loaded into the web view"
* groupId(Optional) : "Based on this group id, system will group the notification"
* channelId(optional) : "This channel id will be used to send notification"
* image(optional) : "This image will be displayed on notification panel"
* label(optional) : "NotificationDTO label"
* priority(optional) : "NotificationDTO priority. If notification priority not mentioned,
* Then default priority will be assigned to the notification"
* }
*/
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
boolean isForeGround = false;
super.onMessageReceived(remoteMessage);
// Fetching data part from the notification
Map<String, String> data = remoteMessage.getData();
String message = data.get("message");
String id = data.get("notificationId");
int notificationId;
// If notification id is empty then no need to show a notification
if (id == null || id.isEmpty()) {
return;
} else {
notificationId = Integer.parseInt(id);
}
if (message == null || message.equals("")) {
message = getString(R.string.default_notification_message);
}
String notificationURL = data.get("notificationURL");
String title = data.get("title");
// Group id should be a string
String groupKey = AppConstant.GROUP_KEY_NOTIFICATION;
if (data.get("groupKey") != null) {
groupKey = data.get("groupKey");
}
// Current we have only one channel with id `general_notification_id`
String channelId = data.get("channelId");
String label = data.get("label");
String image = data.get("image");
/*
* Notification priority(String Value) should be one of the following
* PRIORITY_HIGH/PRIORITY_LOW/PRIORITY_MAX/PRIORITY_MIN
* If no priority mentioned, system will automatically assign the default priority
*/
String priority = data.get("priority");
int notificationPriority = 0;
if (priority != null && !priority.isEmpty()) {
priority = priority.toUpperCase();
switch (priority) {
case "PRIORITY_HIGH":
notificationPriority = NotificationCompat.PRIORITY_HIGH;
break;
case "PRIORITY_LOW":
notificationPriority = NotificationCompat.PRIORITY_LOW;
break;
case "PRIORITY_MAX":
notificationPriority = NotificationCompat.PRIORITY_MAX;
break;
case "PRIORITY_MIN":
notificationPriority = NotificationCompat.PRIORITY_MIN;
break;
default:
notificationPriority = NotificationCompat.PRIORITY_DEFAULT;
break;
}
}
/*
* Category should be from the following list.
* Because system will sort the notification based on the category.
*
* CATEGORY_ALARM,CATEGORY_CALL,CATEGORY_MESSAGE,CATEGORY_EMAIL,CATEGORY_EVENT,
* CATEGORY_PROMO,CATEGORY_ALARM,CATEGORY_PROGRESS,CATEGORY_SOCIAL,CATEGORY_ERROR,
* CATEGORY_TRANSPORT,CATEGORY_SYSTEM,CATEGORY_SERVICE,CATEGORY_REMINDER,
* CATEGORY_RECOMMENDATION,CATEGORY_STATUS
*/
String category = data.get("category");
String notificationCategory = "";
if (category != null && !category.isEmpty()) {
category = category.toUpperCase();
switch (category) {
case "CATEGORY_ALARM":
notificationCategory = NotificationCompat.CATEGORY_ALARM;
break;
case "CATEGORY_CALL":
notificationCategory = NotificationCompat.CATEGORY_CALL;
break;
case "CATEGORY_MESSAGE":
notificationCategory = NotificationCompat.CATEGORY_MESSAGE;
break;
case "CATEGORY_EMAIL":
notificationCategory = NotificationCompat.CATEGORY_EMAIL;
break;
case "CATEGORY_EVENT":
notificationCategory = NotificationCompat.CATEGORY_EVENT;
break;
case "CATEGORY_PROMO":
notificationCategory = NotificationCompat.CATEGORY_PROMO;
break;
case "CATEGORY_PROGRESS":
notificationCategory = NotificationCompat.CATEGORY_PROGRESS;
break;
case "CATEGORY_SOCIAL":
notificationCategory = NotificationCompat.CATEGORY_SOCIAL;
break;
case "CATEGORY_ERROR":
notificationCategory = NotificationCompat.CATEGORY_ERROR;
break;
case "CATEGORY_TRANSPORT":
notificationCategory = NotificationCompat.CATEGORY_TRANSPORT;
break;
case "CATEGORY_SYSTEM":
notificationCategory = NotificationCompat.CATEGORY_SYSTEM;
break;
case "CATEGORY_SERVICE":
notificationCategory = NotificationCompat.CATEGORY_SERVICE;
break;
case "CATEGORY_RECOMMENDATION":
notificationCategory = NotificationCompat.CATEGORY_RECOMMENDATION;
break;
case "CATEGORY_REMINDER":
notificationCategory = NotificationCompat.CATEGORY_REMINDER;
break;
case "CATEGORY_STATUS":
notificationCategory = NotificationCompat.CATEGORY_STATUS;
break;
}
}
// Default notification visibility is private
String visibility = data.get("visibility");
int notificationVisibility = 0;
if (visibility != null && !visibility.isEmpty()) {
visibility = visibility.toUpperCase();
switch (visibility) {
case "VISIBILITY_PUBLIC":
notificationVisibility = NotificationCompat.VISIBILITY_PUBLIC;
break;
case "VISIBILITY_SECRET":
notificationVisibility = NotificationCompat.VISIBILITY_SECRET;
break;
default:
notificationVisibility = NotificationCompat.VISIBILITY_PRIVATE;
break;
}
}
//creating default notification url for grouped notifications
// if notification grouped, user cannot go the url corresponding to the each notification therefore assign a common url for the notification
String defaultNotificationURL = "https://something.com"
// Creating notification object
NotificationDTO notificationDTO = new NotificationDTO(
notificationId,
groupKey,
message,
notificationURL,
channelId,
image,
label,
notificationPriority,
title,
notificationCategory,
notificationVisibility,
defaultNotificationURL);
// Checking app is in foreground or background
// if the app in the foreground this message service send a broadcast message
// else app will create a notification in notification panel
try {
isForeGround = new ForegroundCheckTask().execute(this).get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//Android implement new grouping and channel mechanisms after android API version 24,
//So we need to implement different notification settings for both above 24 and below 24
if (android.os.Build.VERSION.SDK_INT >= 24) {
createNotificationForAPILevelAbove24(notificationDTO, isForeGround);
} else {
createNotificationForAPILevelBelow24(notificationDTO, isForeGround);
}
}
/**
* Creating notification for api level above 24
*
* @param notificationDTO NotificationDTO
* @param isForeGround Boolean
*/
private void createNotificationForAPILevelAbove24(NotificationDTO notificationDTO, Boolean isForeGround) {
Log.d(TAG, String.valueOf(isForeGround));
if (isForeGround) {
Intent intent = new Intent(MY_NOTIFICATION);
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
} else {
int requestID = (int) System.currentTimeMillis();
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
PendingIntent resultIntent = PendingIntent.getActivity(this, requestID, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
String defaultChannel = getString(R.string.general_notification_id);
NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this, defaultChannel);
mNotificationBuilder.setSmallIcon(R.drawable.ic_stat_notification);
mNotificationBuilder.setColor(getResources().getColor(R.color.colorPrimary));
mNotificationBuilder.setContentTitle(notificationDTO.getTitle());
mNotificationBuilder.setContentText(notificationDTO.getMessage());
mNotificationBuilder.setGroup(notificationDTO.getGroupKey());
mNotificationBuilder.setAutoCancel(true);
mNotificationBuilder.setSound(notificationSoundURI);
mNotificationBuilder.setPriority(notificationDTO.getPriority());
if (notificationDTO.getImage() != null) {
Bitmap bitmap = getBitmapFromUrl(notificationDTO.getImage());
mNotificationBuilder.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap));
}
mNotificationBuilder.setContentIntent(resultIntent);
if (notificationDTO.getCategory() != null) {
mNotificationBuilder.setCategory(notificationDTO.getCategory());
}
mNotificationBuilder.setVisibility(notificationDTO.getVisibility());
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
boolean areNotificationsEnabled = notificationManager.areNotificationsEnabled();
String appPushEnabled = String.valueOf(areNotificationsEnabled);
notificationManager.notify(notificationDTO.getId(), mNotificationBuilder.build());
// Creating notification summary for grouping notifications
Notification summaryNotification =
new NotificationCompat.Builder(this, defaultChannel)
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_stat_notification)
//specify which group this notification belongs to
.setGroup(notificationDTO.getGroupKey())
//set this notification as the summary for the group
.setGroupSummary(true)
//automatically remove the notifications from the notification tray
.setAutoCancel(true)
.build();
notificationManager.notify(getString(R.string.app_name), SUMMARY_ID, summaryNotification);
}
}
/**
* Handling notification for api level below 24
*
* @param notificationDTO NotificationDTO
* @param isForeGround Boolean
*/
private void createNotificationForAPILevelBelow24(NotificationDTO notificationDTO, Boolean isForeGround) {
if (isForeGround) {
Intent intent = new Intent(MY_NOTIFICATION);
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
} else {
//Grouping notifications
String storedNotifications = SharedPrefManager.getInstance(this).getNotifications();
JSONArray notificationArray;
try {
boolean isDuplicateNotification = false;
JSONObject notificationObject = new JSONObject();
notificationObject.put("notificationId", notificationDTO.getId());
notificationObject.put("description", notificationDTO.getMessage());
notificationObject.put("title", notificationDTO.getTitle());
if (storedNotifications != null && !storedNotifications.equals("")) {
Log.d(TAG, storedNotifications);
notificationArray = new JSONArray(storedNotifications);
for (int i = 0; i < notificationArray.length(); i++) {
JSONObject json = notificationArray.getJSONObject(i);
if (json.getInt("notificationId") == notificationDTO.getId()) {
isDuplicateNotification = true;
break;
}
}
} else {
notificationArray = new JSONArray();
}
if (isDuplicateNotification) {
//Notification already added to the tray
return;
}
notificationArray.put(notificationObject);
SharedPrefManager.getInstance(this).setNotificationDetails(notificationArray.toString());
Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder summary = new NotificationCompat.Builder(this);
summary.setSmallIcon(R.drawable.ic_stat_notification);
summary.setGroup(notificationDTO.getGroupKey());
summary.setAutoCancel(true);
summary.setPriority(notificationDTO.getPriority());
summary.setColor(ContextCompat.getColor(this, R.color.colorPrimary));
summary.setSound(notificationSoundURI);
summary.setContentTitle(notificationDTO.getTitle());
summary.setContentText(notificationDTO.getMessage());
summary.setPriority(notificationDTO.getPriority());
if (notificationDTO.getCategory() != null) {
summary.setCategory(notificationDTO.getCategory());
}
summary.setVisibility(notificationDTO.getVisibility());
if (notificationDTO.getImage() != null) {
Bitmap bitmap = getBitmapFromUrl(notificationDTO.getImage());
summary.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap));
}
/*
* This is used to pass notification url to the main class of the application.
* Based on this url MainActivity load the corresponding url into the web view
*/
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
/*
* checking more than 2 notifications received by the system,
* then this will create a summary of that notifications.
* else create a single notification
*/
if (notificationArray.length() > 1) {
summary.setGroupSummary(true);
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
inboxStyle.setBigContentTitle(getString(R.string.app_name));
summary.setStyle(inboxStyle);
int messageCount;
for (messageCount = 0; messageCount < notificationArray.length(); messageCount++) {
JSONObject json = notificationArray.getJSONObject(messageCount);
inboxStyle.addLine(json.getString("title") + " " + json.getString("description"));
}
inboxStyle.setSummaryText(String.valueOf
(messageCount) + " notifications");
summary.setNumber(messageCount);
summary.setContentText(String.valueOf(messageCount + " notifications"));
intent.putExtra("notificationURL", notificationDTO.getDefaultNotificationUrl());
} else {
intent.putExtra("notificationURL", notificationDTO.getNotificationURL());
}
PendingIntent resultIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
summary.setContentIntent(resultIntent);
/*
* One cancel intent is used to clear the notifications stored in
* the shared preferences when user delete the notifications.
*/
Intent onCancelIntent = new Intent(this, OnCancelBroadcastReceiver.class);
PendingIntent onDismissPendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 0, onCancelIntent, 0);
summary.setDeleteIntent(onDismissPendingIntent);
notificationManager.notify(getString(R.string.app_name), SUMMARY_ID, summary.build());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Used to load image from notification
*
* @param imageUrl String
* @return Bitmap
*/
public Bitmap getBitmapFromUrl(String imageUrl) {
try {
URL url = new URL(imageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (Exception e) {
return null;
}
}
}
通知对象类如下:
public class NotificationDTO {
private String groupKey, message, notificationURL, channelId;
private String image, label, title, category,defaultNotificationUrl;
private int priority, id, visibility;
public NotificationDTO(
int id,
String groupKey,
String message,
String notificationURL,
String channelId,
String image,
String label,
int priority,
String title,
String category,
int visibility,
String defaultNotificationUrl) {
this.groupKey = groupKey;
this.message = message;
this.id = id;
this.notificationURL = notificationURL;
this.channelId = channelId;
this.image = image;
this.label = label;
this.priority = priority;
this.title = title;
this.category = category;
this.visibility = visibility;
this.defaultNotificationUrl = defaultNotificationUrl;
}
public String getGroupKey() {
return groupKey;
}
public String getMessage() {
return message;
}
public String getNotificationURL() {
return notificationURL;
}
public String getChannelId() {
return channelId;
}
public String getLabel() {
return label;
}
public String getImage() {
return image;
}
public int getPriority() {
return priority;
}
public String getTitle() {
return title;
}
public String getCategory() {
return category;
}
public int getId() {
return id;
}
public int getVisibility() {
return visibility;
}
public String getDefaultNotificationUrl() {
return defaultNotificationUrl;
}
}
SharedPreference管理器如下:
public class SharedPrefManager {
private static final String KEY_FCM_TOKEN = "keyFCMToken";
private static final String KEY_NOTIFICATIONS = "keyNotifications";
private static SharedPrefManager mInstance;
private static Context mContext;
private SharedPrefManager(Context context) {
mContext = context;
}
public static synchronized SharedPrefManager getInstance(Context context) {
if (mInstance == null) {
mInstance = new SharedPrefManager(context);
}
return mInstance;
}
public void setNotificationDetails(String descriptions) {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(KEY_NOTIFICATIONS, descriptions);
editor.apply();
}
public void setFCMToken(String fcmToken) {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(KEY_FCM_TOKEN, fcmToken);
editor.apply();
}
public String getNotifications() {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(KEY_NOTIFICATIONS, null);
}
public String getFCMToken() {
SharedPreferences sharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(KEY_FCM_TOKEN, null);
}
}
为了创建通知通道,我使用了扩展应用程序类的应用程序控制器类。
public class AppController extends Application {
public static final String TAG = AppController.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
/*
Define notification channels here.
*/
//NotificationDTO channel is necessary
//create a notification channel id in res/values/strings.xml
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String defaultChannel = getString(R.string.general_notification_id);
String channelName = getString(R.string.general_notification_name);
// String miscellaneousChannel = getString(R.string.miscellaneous_notification_id);
// String miscellaneousChannelName = getString(R.string.miscellaneous_notification_name);
NotificationManager notificationManager =
getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(new NotificationChannel(defaultChannel,
channelName, NotificationManager.IMPORTANCE_DEFAULT));
}
}
}
实施为清除共享首选项的广播接收器,如下所示
public class OnCancelBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("ON_CANCEL","Cancelled");
SharedPrefManager.getInstance(context).setNotificationDetails("");
}
}