I am using pyFCM on a remote server to push event notifications to my app. The notification has a data payload as well, and this is needed for making some UI changes.
When the server app sends the app a notification, there is a notification that pops up on the phone, pressing which causes the default activity to be opened. Also, I have overridden the onMessageReceived in the service extending FirebaseMessagingService, and the data payload is then taken from the message and processed.
I have seen multiple questions on SO like these:
How to open Fragment on click of push notification
Open fragment from notification when the app in background
How to open fragment page, when pressed a notification in android
Open a fragment when press a notification in android
One thing in common with all the above SO questions is that they involve building a notification using Notification.Builder, and then setting up an Activity to open (or it's onNewIntent to be triggered depending on whether it's android:launchMode is "singleTop" in the Manifest ) . My distinct understanding is that this would work if FCM were used to send the app a data message and not a notification.
The question(s)
What I really fail to understand is how setting up a notification after the data is received from FCM helps me change the behaviour of the notification received from FCM's push. Is that even possible, or have I gotten something wrong?
The second part to this question is: How do I handle a user click on a push notification to load a specific fragment in my activity (when I am not the one creating the fragment in the app - just received via FCM)?
This is the current code that runs in my app.
The onMessageReceived code is :
#Override
public void onMessageReceived(RemoteMessage remoteMessagenew) {
this.remoteMessage = remoteMessagenew;
super.onMessageReceived (remoteMessage);
database = MyDatabase.getDatabase(getApplication());
appuser = database.AppDao().getUserDetails();
Log.d(TAG, "onMessageReceived: ");
Log.d (TAG, "onMessageReceived: #########################################################");
Log.d (TAG, "onMessageReceived: #########################################################");
Log.d (TAG, "onMessageReceived: Message Data is : " + remoteMessage.getData ());
Map messageData = remoteMessage.getData ();
// Broadcast
args = new Bundle ();
int stage = 0 ;
Log.d (TAG, "onMessageReceived: ");
if(messageData.containsKey ("otherid")) {
incidenthandler(messageData);
} else if(messageData.containsKey ("itemid")) {
itemhandler(messageData);
}
}
The item handler to handle the data to refresh on the app screen
private void itemhandler(Map messageData) {
messagebody = remoteMessage.getNotification().getBody();
if(messageData.containsKey("itemid")) {
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: UPDATING");
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: #######################");
String item = (String ) messageData.get("itemid");
JsonParser jsonParser = new JsonParser();
JsonObject jsonObject = (JsonObject)jsonParser.parse(item);
item refreshedItem = parseFromJson(jsonObject);
database.revivDao().upsert(refreshedItem);
} else {
// this really shouldn't happen, but putting in a scenario where this does
// syncing the db with the app
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: REFRESHING ");
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: #######################");
// this triggers a call to my intentservice to refresh the db
Log.d(TAG, "itemhandler: refreshing items");
Intent intent = new Intent ("refreshitems");
Bundle clickdata = new Bundle();
data.putString("item_sub1", item_sub1);
data.putString("item_sub1", item_sub1);
intent.putExtra("data", clickdata) ; // add Bundle to Intent
localBroadcastManager.getInstance(getApplication()).sendBroadcast(intent); // Broadcast Intent
}
String itemid = messageData.get("itemid").toString();
try{
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: sending localbroadcast");
Log.d(TAG, "itemhandler: #######################");
Log.d(TAG, "itemhandler: #######################");
// this is where I try and switch to the screen to display some info - if the app is running
Intent notificationIntent = new Intent("switchtofragment");
notificationIntent.putExtra("launchitemfragment", true);
notificationIntent.putExtra("itemid", itemid);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if(localBroadcastManager == null) localBroadcastManager = LocalBroadcastManager.getInstance(getApplication());
localBroadcastManager.sendBroadcast(notificationIntent);
// now override the notification to load the fragment on click
setupNotification(itemid, messagebody);
notificationManager.notify(1, builder.build());
} catch (NullPointerException e){
return;
}
}
Finally, the function to handle the notification:
private void setupNotification(String housecallid, String message) {
//Log.d(TAG, "setting up notification");
String idChannel = ANDROID_CHANNEL_ID;
Context context = getApplicationContext();
Intent notificationIntent = new Intent(getApplicationContext(), Reviv.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
notificationIntent.putExtra("housecallid", housecallid);
notificationIntent.putExtra("launchhousecallfragment", true);
Bundle extras = new Bundle();
extras.putBoolean("launchhousecallfragment", true);
extras.putString("housecallid", housecallid);
notificationIntent.putExtras(extras);
notificationIntent.putExtra("data", extras);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
if(notificationManager == null )
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(context, null);
builder. setSmallIcon(R.drawable.heart)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
R.drawable.heart))
.setContentIntent(pendingIntent)
.setContentTitle("Reviv")
.setContentText(message);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if(notificationChannel == null) {
notificationChannel = new NotificationChannel(idChannel, context.getString(R.string.app_name), importance);
// Configure the notification channel.
notificationChannel.setDescription("Reviv Housecall Notification");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
notificationManager.createNotificationChannel(notificationChannel);
builder.setChannelId(idChannel);
builder.setAutoCancel(true);
}
} else {
builder.setContentTitle(context.getString(R.string.app_name))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(context, R.color.transparent))
.setVibrate(new long[]{100, 250})
.setLights(Color.RED, 500, 5000)
.setAutoCancel(true);
}
builder.setContentIntent(pendingIntent);
}
You could rather send data messages only instead of sending push messages + payload and receive all notifications in onMessageReceived whether the app is in foreground or background. You can then setup the notification using the setupNotification function you have written and check for extras in the start of the Reviv activity and replace fragments accordingly.
I'm not sure but about this approach using push notifications but here's a suggestion: Try to see if you receive the data payload (check for getIntent().getExtras()) which you are sending with the push notification in the activity which is opened by default on clicking the push notification.
Related
I have a service that calls a BroadcastReceiver. It was working fine but after I changed the back-end system for handling notification ids, the final notification update which should update a notification to tell the user that a process has finished. However, that final update would not pass through unless I place a breakpoint in debugging mode in the method handling those notifications. If a breakpoint is not placed or debug mode is not active, that final notification update would not happen and the notification would display its last state which tells the user that the process has not finished, which is not the case.
Here's the method, and the Log.d()s show that the manager.notify() method was called properly to update the notification to signify that the process has finished.
#Override
public void onReceive(Context context, Intent intent) {
Download data = null;
try {
data = (Download) intent.getSerializableExtra(Commons.ARGS.DATA);
} catch (ClassCastException | NullPointerException e) {
e.printStackTrace();
}
switch (intent.getIntExtra(Commons.ARGS.RESULT, Commons.ARGS.FAILED)) {
case Commons.ARGS.DESTROY:
Log.w(TAG, "Service was destroyed");
if (notifications && manager != null) {
Download[] remaining = (Download[]) intent.getParcelableArrayExtra(Commons.ARGS.DATA);
if (remaining == null) break;
for (Download d : remaining) {
// Download has failed since service was destroyed; was never called and has no relations to this case. manager.notify() is called here to update the notification as "cancelled" btw
}
}
break;
case Commons.ARGS.ERR_LOAD:
Log.w(TAG, "Failed to load queue");
break;
}
if (data == null) return;
if (notifications) {
Intent i = new Intent(context, MainActivity.class);
NotificationCompat.Builder builder = new NotificationCompat.Builder(activity, Commons.Notif.DOWNLOAD_PROGRESS)
.setContentIntent(PendingIntent.getActivity(activity, 0, i, 0))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.ic_notif)
.setContentTitle(data.title)
.setColor(context.getResources().getColor(R.color.Accent))
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setGroup(CHANNEL_ID);
if (manager == null) {
manager = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Downloads in Progress", NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("All downloads are displayed here");
channel.enableLights(false);
channel.enableVibration(false);
manager.createNotificationChannel(channel);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID);
}
// Notification builder styling is handled here
manager.notify(0, new NotificationCompat.Builder(activity, Commons.Notif.DOWNLOAD_PROGRESS)
.setChannelId(CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notif)
.setGroup(CHANNEL_ID)
.setColor(activity.getResources().getColor(R.color.Accent))
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setGroupSummary(true)
.setContentText("All downloads have finished.")
.setContentTitle("Done")
.build());
manager.notify(DOWNLOAD_BASE_ID | (data.id & 0x00FF_FFFF), builder.build());
Log.d(TAG, "Notification updated");
}
}
Thanks in advance :P
I do not know what exactly is happening but canceling the notification using manager.cancel() before updating it with the success/final notification solved the problem.
int id = // Notification id
if (success) {
Log.d(TAG, "Success Notification updated");
manager.cancel(id);
} else Log.d(TAG, "Notification updated");
manager.notify(id, builder.build());
I used firebase to build My project.
It will also use the FCM (firebase cloud message).
But there is a problem. I can't handle the FCM (create my custom notificaion) when app is in background.
The official site tutorial said that
case 1: App foreground -> override the "onMessageReceived()" to create your custom notification.
case 2: App background -> System will create the notification directly. We needn't and can't do anything. Because it doesn't trigger the "onMessageReceived()" in this case.
However if I can do nothing when app is background, I can't create my custom notification. (e.g. After Users click the notification and it will pop up a window to show detail information.)
So how do I handle notifications with FCM when app is in background?
There is a bad news. Google change the Firebase source code in version 'com.google.firebase:firebase-messaging:11.6.0'.
handelIntent is "public final void method" now. which means we can't override it .
If you want to use the solution, change the version to be "com.google.firebase:firebase-messaging:11.4.2"
Try my way. It can perfectly work on the project build version is Android 6.0 above(api level 23) and I have tried it already.
There is better way than official site tutorial
The official site said that the notification will be created by system when app is in background. So you can't handle it by overriding the "onMessageReceived()". Because the "onMessageReceived()" is only triggered when app is in foreground.
But the truth is not. Actually the notificaions (when app is in background) are created by Firebase Library.
After I traced the firebase library code. I find a better way.
Step 1. Override the "handleIntent()" instead of "onMessageReceived()" in FirebaseMessagingService
why:
Because the method will be trigger either app is in foreground or the background. So we can handle FCM message and create our custom notifications in both cases.
#Override
public void handleIntent(Intent intent) {
Log.d( "FCM", "handleIntent ");
}
Step 2. Parse the message from FCM
how:
If you don't know the format of the message you set. Print it and try to parse it.
Here is the basic illustration
Bundle bundle = intent.getExtras();
if (bundle != null) {
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
Log.d("FCM", "Key: " + key + " Value: " + value);
}
}
Step 2. Remove the notifications created by Firebase library when the app is in background
why:
We can create our custom notification. But the notification created by Firebase Library will still be there (Actually it created by ""super.handleIntent(intent)"". There is detail explaination below.). Then we'll have two notifcations. That is rather weird. So we have to remove the notificaion created by Firebase Library
how (project build level is Android 6.0 above):
Recognize the notifications which we want to remove and get the informaion. And use the "notificationManager.cancel()" to remove them.
private void removeFirebaseOrigianlNotificaitons() {
//check notificationManager is available
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null )
return;
//check api level for getActiveNotifications()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
//if your Build version is less than android 6.0
//we can remove all notifications instead.
//notificationManager.cancelAll();
return;
}
//check there are notifications
StatusBarNotification[] activeNotifications =
notificationManager.getActiveNotifications();
if (activeNotifications == null)
return;
//remove all notification created by library(super.handleIntent(intent))
for (StatusBarNotification tmp : activeNotifications) {
Log.d("FCM StatusBarNotification",
"StatusBarNotification tag/id: " + tmp.getTag() + " / " + tmp.getId());
String tag = tmp.getTag();
int id = tmp.getId();
//trace the library source code, follow the rule to remove it.
if (tag != null && tag.contains("FCM-Notification"))
notificationManager.cancel(tag, id);
}
}
The my whole sample code:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static int notificationCount=0;
#Override
public void handleIntent(Intent intent) {
//add a log, and you'll see the method will be triggered all the time (both foreground and background).
Log.d( "FCM", "handleIntent");
//if you don't know the format of your FCM message,
//just print it out, and you'll know how to parse it
Bundle bundle = intent.getExtras();
if (bundle != null) {
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
Log.d("FCM", "Key: " + key + " Value: " + value);
}
}
//the background notification is created by super method
//but you can't remove the super method.
//the super method do other things, not just creating the notification
super.handleIntent(intent);
//remove the Notificaitons
removeFirebaseOrigianlNotificaitons();
if (bundle ==null)
return;
//pares the message
CloudMsg cloudMsg = parseCloudMsg(bundle);
//if you want take the data to Activity, set it
Bundle myBundle = new Bundle();
myBundle.putSerializable(TYPE_FCM_PLATFORM, cloudMsg);
Intent myIntent = new Intent(this, NotificationActivity.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.putExtras(myBundle);
PendingIntent pendingIntent = PendingIntent.getActivity(this, notificationCount, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//set the Notification
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.icon)
.setContentTitle(cloudMsg.getTitle())
.setContentText(cloudMsg.getMessage())
.setAutoCancel(true)
.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationCount++, notificationBuilder.build());
}
/**
* parse the message which is from FCM
* #param bundle
*/
private CloudMsg parseCloudMsg(Bundle bundle) {
String title = null, msg=null;
//if the message is sent from Firebase platform, the key will be that
msg = (String) bundle.get("gcm.notification.body");
if(bundle.containsKey("gcm.notification.title"))
title = (String) bundle.get("gcm.notification.title");
//parse your custom message
String testValue=null;
testValue = (String) bundle.get("testKey");
//package them into a object(CloudMsg is your own structure), it is easy to send to Activity.
CloudMsg cloudMsg = new CloudMsg(title, msg, testValue);
return cloudMsg;
}
/**
* remove the notification created by "super.handleIntent(intent)"
*/
private void removeFirebaseOrigianlNotificaitons() {
//check notificationManager is available
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null )
return;
//check api level for getActiveNotifications()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
//if your Build version is less than android 6.0
//we can remove all notifications instead.
//notificationManager.cancelAll();
return;
}
//check there are notifications
StatusBarNotification[] activeNotifications =
notificationManager.getActiveNotifications();
if (activeNotifications == null)
return;
//remove all notification created by library(super.handleIntent(intent))
for (StatusBarNotification tmp : activeNotifications) {
Log.d("FCM StatusBarNotification",
"tag/id: " + tmp.getTag() + " / " + tmp.getId());
String tag = tmp.getTag();
int id = tmp.getId();
//trace the library source code, follow the rule to remove it.
if (tag != null && tag.contains("FCM-Notification"))
notificationManager.cancel(tag, id);
}
}
}
However if I can do nothing when app is background, I can't create my custom notification. (e.g. After Users click the notification and it will pop up a window to show detail information.)
So how do I handle notifications with FCM when app is in background?
First, you need to create correct message payload that you send to fcm server. Example:
{
"to": "topic_name",
"priority": "high",
"data": {
"field1": "field1 value"
"field2": "field2 value"
}
"notification" : {
"body" : "Lorem ipsum",
"title" : "sampke title"
"click_action": "SHOW_DETAILS"
}
}
data payload is actual data you want to show as message details after user clicks on notification, notification payload represents how generated notification should look (there are much more attributes possible to set), you don't need to build notification by yourself, you only need to set it properties here.
To show your activity after user taps on notication, you need to set intent filter corresponding to click_action:
<intent-filter>
<action android:name="SHOW_DETAILS"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
so activity that have above intent filter will be launched automatically when user taps to notification.
Last step is to retrieve data when activity is launched after notification tap. It's pretty easy. Custom data is passed to activity via bundle. Inside onCreate method for your activity do something like that:
Bundle bundle = getIntent().getExtras();
if(bundle.getString("action").equals("SHOW_DETAILS")) /*This indicates activity is launched from notification, not directly*/
{
//Data retrieved from notification payload send
String filed1 = bundle.getString("field1");
String filed2 = bundle.getString("field2");
}
All of above is valid if app is not running or it's in background. If your app is foreground, no notification will be created. Instead, you will receive onMessageReceived() event so you can handle the same data there (I guess you know how).
Reference:
https://firebase.google.com/docs/cloud-messaging/http-server-ref
https://github.com/firebase/quickstart-android/tree/master/messaging
You need to use FCM data messages in order to create custom notification in a android app.Even your app is in background, onMessageReceived will be called, so you can process the data and show the custom notification.
https://firebase.google.com/docs/cloud-messaging/android/receive
Data message format which has to be sent from server:
{"message":{
"token":"Your Device Token",
"data":{
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
}
}
}
FCM Won't send a background notification if your app is killed any more, and as you described in your answer about the handleIntent() solution It may work for some devices and for some old version of the FCM, also if you #override method that doesn't described in the official doc's of firebase you may struggle some problems here, and you use it on your own risk!.
What is the solution?
You need to use your own push-notification-service beside FCM like Telegram.
OR using SyncAdapter beside GCM like Gmail.
So if you need it to work successfully like those apps, you have to use your own hack.
public class FirebaseMessageReceiver extends FirebaseMessagingService{
private static final String TAG = "main";
String s12;
String channel_id = "general";
Intent intent;
#Override
public void onNewToken(#NonNull String token)
{
Log.d(TAG, "Refreshed token: " + token);
}
#Override
public void
onMessageReceived(RemoteMessage remoteMessage) {
s12=remoteMessage.getNotification().getClickAction();
Log.d("tttt",(remoteMessage.getData().toString()));
Log.d("ttttttt",(remoteMessage.getNotification().toString()));
if (remoteMessage.getNotification() != null) {
showNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody());
}
//
}
public void handleIntent(Intent intent)
{
try
{
if (intent.getExtras() != null)
{
RemoteMessage.Builder builder = new RemoteMessage.Builder("FirebaseMessageReceiver");
for (String key : intent.getExtras().keySet())
{
builder.addData(key, intent.getExtras().get(key).toString());
}
onMessageReceived(builder.build());
}
else
{
super.handleIntent(intent);
}
}
catch (Exception e)
{
super.handleIntent(intent);
}
}
private RemoteViews getCustomDesign(String title, String message) {
RemoteViews remoteViews = new RemoteViews(getApplicationContext().getPackageName(), R.layout.notification);
remoteViews.setTextViewText(R.id.title111, title);
remoteViews.setTextViewText(R.id.message111, message);
remoteViews.setImageViewResource(R.id.icon111, R.drawable.favicon);
return remoteViews;
}
// Method to display the notifications
public void showNotification(String title, String message) {
intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(s12));
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent notifyIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Log.d("notifyy",notifyIntent.toString());
NotificationCompat.Builder builder
= new NotificationCompat
.Builder(getApplicationContext(),
channel_id)
.setSmallIcon(R.drawable.favicon)
.setAutoCancel(true)
.setVibrate(new long[]{1000, 1000, 1000, 1000, 1000})
.setOnlyAlertOnce(true)
.setContentIntent(notifyIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
builder = builder.setContent(getCustomDesign(title, message));
}
else {
builder = builder.setContentTitle(title)
.setContentText(message)
.setSmallIcon(R.drawable.favicon);
}
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Check if the Android Version is greater than Oreo
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(channel_id, "web_app",
NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(
notificationChannel);
}
notificationManager.notify(0, builder.build());
}
}
I'm having a problem using the FCM Push Notification Messaging Service, as I've overridden the handleIntent() method to receive the notification when the app is in the foreground. I am also using the onMessageReceived() method.
But when the app is in the background, I will receive 2 notifications, which one of them only opens up the app and runs the MainActivity while the other is opening up the app how I want it to.
FYI: The notification I receive when I am in the foreground is exactly how I want it to open.
This is the code I've written below :
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private final String NOTIFICATION_TYPE = "type";
private final String NOTIFICATION_ID = "id";
private final String NOTIFICATION_TYPE_PRODUCT_DETAIL = "productdetail";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String title = remoteMessage.getNotification().getTitle();R
String body = remoteMessage.getNotification().getBody();
String token = remoteMessage.getFrom();
Log.d("FireBase TAG: ", token);
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d("FireBaseMessageService","FireBase Data payload : " + remoteMessage.getData());
}
// Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
}
#Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
String type = intent.getExtras().getString(NOTIFICATION_TYPE, "");
int id = 0;
try {
id = Integer.valueOf(intent.getExtras().getString(NOTIFICATION_ID, ""));
} catch (NumberFormatException e) {
e.printStackTrace();
}
//Intents
Intent mainIntent = MainActivity.newIntent(this);
Intent editProfileIntent = EditProfileActivity.newIntent(this);
Intent settingsIntent = SettingsActivity.newIntent(this);
Intent productIntent = ProductActivity.newNotificationIntent(this, id, false, true);
if (UserManager.getSingleton().isUserLoggedIn(getApplicationContext())) {
PendingIntent pendingIntent;
if (type.equalsIgnoreCase(NOTIFICATION_TYPE_PRODUCT_DETAIL)) {
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(mainIntent);
stackBuilder.addNextIntent(productIntent);
editProfileIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_ONE_SHOT);
}
else {
pendingIntent = PendingIntent.getActivity(this, 0, productIntent,
PendingIntent.FLAG_ONE_SHOT);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(intent.getExtras().getString("gcm.notification.title"))
.setContentText(intent.getExtras().getString("gcm.notification.body"))
.setDefaults(NotificationCompat.DEFAULT_VIBRATE)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentIntent(pendingIntent);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(0, builder.build());
}
}
}
I have deleted the NotificationCompat.Builder from the onMessageReceived() method.
But I am still receiving two notifications in the background.
App Gradle :
compile 'com.google.firebase:firebase-core:11.4.2' //Firebase
compile 'com.google.firebase:firebase-messaging:11.4.2' //Firebase Cloud Messaging
I've tried searching for a solution online but unluckily there isn't a solution pointing to Android.
You are handling your Notification stuff into handleIntent(Intent intent). You should probably remove super.handleIntent(intent); to prevent the Firebase system to handle notification while the app is in background.
Solution: remove super.handleIntent(intent);
Just make a sendnotification() method and set whatever parameters you want to pass like body i.e sendnotification(String body). Use pending intent to start you activity and when you click on notification your app parse the data to the launcher activity which is defined in manifest, so once you have data in your launcher activity you can send data to other activity using intent.
I think the .setContentText("") is getting called more than 1 times and are you getting same notification two times?
The notification which works perfectly is generated by your code but when your application is not in foreground android system will generate the notification for you. In this case when you don't have the control to send data in your intent that you were sending to open your desired Activity.
In this case, you have to do some modification on your servers payload. You have to add click_action in your payload, this is how android system will identify the destination activity.
Payload Example:
{ "notification": {
"title":"Notification title",
"body":"Notification body",
"click_action":"<Your_destination_activity>",
}, "data":{
"param1":"value1",
"param2":"value2"
},
"priority":"high",
}
Reference:
https://firebase.google.com/docs/cloud-messaging/http-server-ref
yes,
When you app in background you will receive the push at system tray so system tray will create push with notification title and message.
and when you click on the push your initial launcher activity (which mentioned as launcher in manifest) will open.
you can get your notification data at you launcher activity (bundle).
private void handlePush() {
Intent intent = null;
if (bundle.getString("push_type") != null && bundle.getString("push_type").length() > 0) {
switch (bundle.getString("push_type")) {
case PUSH_TYPE_FOLLOW_USER: {
intent = new Intent(this, ProfileExternalActivity.class);
intent.putExtra(Constants.USER_ID, Integer.parseInt(bundle.getString("id")));
intent.putExtra(Constants.FROM_PUSH_NOTIFICATION_SPLASH, true);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
break;
}
}
if (intent != null)
startActivity(intent);
finish();
}
}
and you need to check activty have data or not
if (bundle != null)
handlePush();
else //your next activity
FYI : https://firebase.google.com/docs/cloud-messaging/android/receive
or
you can get payload object instead of data object inside notification , if you have payload object in your notification object, push all time received at your onMessageReceived().
for people still having this issue:
Here is a hack to prevent this behavior. I've looked all over and there seems to be minimal info about this, but if you save the actual message being sent in shared preferences and then do a check against that value in onRecieve, you can easily prevent this. The only downside is that your user can't send the exact same message two times in a row in the form of a notification (but that would be annoying anyway). example:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
SharedPreferences Settings = getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = Settings.edit();
message = remoteMessage.getNotification().getBody();
from = remoteMessage.getFrom(); //this comes through as the topic, oddly...
if(from.equals("/topics/"+userID+deviceID+"all")) {
if(!message.equals(Settings.getString("messageall",null))) {//this filters any dupe messages
utils.postNotification(title, message, context, extra, "messages");//create notification
editor.putString("messageall", message);//always update to the last message
editor.commit();
}
}
}
I am using FCM to push notification. I am passing intent to launch new activity when notification is clicked.when app is in foreground,app works fine and intent launch new activity, but when app is in background, it does not launch new activity but launch instance of default activity.
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
//Displaying data in log
//It is optional
Log.d(TAG, "From: " + remoteMessage.getFrom());
Log.d(TAG, "Notification Message Body: " + remoteMessage.getNotification().getBody());
//Calling method to generate notification
sendNotification(remoteMessage.getNotification().getBody());
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, SecActivity.class);
intent.putExtra("started_from","notification");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Firebase Push Notification")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
}
}
Hope you are trying to launch the mainactivity when the message is received. When the app is resumed from background your current activity is getting cleared.
From the documentation for FLAG_ACTIVITY_CLEAR_TOP:
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
Try removing this flag.
I too had this same problem but i managed to have it fix with this ,
In your default activity mentioned in the manifest do this in the onCreate
if (bundle != null) {
if ((String) bundle.get("tag") != null) {
String tag = (String) bundle.get("tag");
if (tag.equals("abc")) {
Intent intent = new Intent(SplashActivity.this, MessageDetailsActivity.class);
startActivity(intent);
} else if (tag.equals("def")) {
openSpecificActivity(tag, (String) bundle.get("id"));
}
} else {
Intent i = new Intent(SplashActivity.this, HomeActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}
i got a solution for that.
just put below code in oncreate method of launcher activity.
if (bundle != null) {
String value = bundle.getString("key");
if (value != null) {
startActivity(new Intent(MainActivity.this, secActivity.class));
}
}
when app is in background or killed,FCM will not call onmessagerecieved method,but it will send data to system tray to display notification.so datapayload(sent from fcm console) will not be handled by onmessagerecieved method.when user click on notification,it will launch default activity of app and datapayload will be passed by intent .so making change in oncreate method of launcher activity(as above)we can get datapayload even when app is in background or killed.(ex key is sent by fcm console).when app is in foreground datapayload and will be handled by onmessagerecieved method of fcm service.
Based upon Antinio's answer
https://stackoverflow.com/a/37845174/4454119
Why is this happening?
There are two types of messages in FCM (Firebase Cloud Messaging):
display-messages: These messages trigger the onMessageReceived() callback only when your app is in foreground
data-messages: Theses messages trigger the onMessageReceived() callback even if your app is in foreground/background/killed
Firebase team have not developed a UI to send data-messages to your devices, yet.
So you need to use data-messages..
In FCM you have two types of messages
Notification Messages
Data Messages
Use notification messages when you want FCM to handle displaying a notification on your client app's behalf. Use data messages when you want to process the messages in your client app.
If you need to process your message before sending it to the system tray, it's better to use Data messages, as for these types of messages, the callback first reaches the onMessageRecieved method before going to the system tray.
Use this:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
IN YOUR SERVICE
"to": token,
"notification": {
"title": "Title,
"body": "Body"
},
"data" : {
"update": "yes"
}
IN ANDROID KOTLIN
val intent = Intent(this,MainActivity::class.java)
intent.putExtra("update","yes")
......
I have an Android application, where I'm using some method to show notification number on app icon. Now I want to set that number when notification is received.
I thought that I should set the number when notification received so I set it inside onMessageReceived method. But, my problem is when my app is in background, onMessageReceived method not called, so the notification number isn't set.
Following is my code. I set the number inside onMessageReceived. I already tested setBadge method and can verify that it is working. The problem is onMessageReceived is not called so setBadge is also not called, which doesn't set the number.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
Log.d(TAG, "From: " + remoteMessage.getFrom());
Conts.notificationCounter ++;
//I am setting in here.
setBadge(getApplicationContext(),Conts.notificationCounter );
Log.e("notificationNUmber",":"+ Conts.notificationCounter);
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
}
// Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
}
// [END receive_message]
public static void setBadge(Context context, int count) {
String launcherClassName = getLauncherClassName(context);
if (launcherClassName == null) {
Log.e("classname","null");
return;
}
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", count);
intent.putExtra("badge_count_package_name", context.getPackageName());
intent.putExtra("badge_count_class_name", launcherClassName);
context.sendBroadcast(intent);
}
public static String getLauncherClassName(Context context) {
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
String pkgName = resolveInfo.activityInfo.applicationInfo.packageName;
if (pkgName.equalsIgnoreCase(context.getPackageName())) {
String className = resolveInfo.activityInfo.name;
return className;
}
}
return null;
}
When I searched this issue, I found that if the coming message is display message then onMessageReceived is called only if app is foreground. But if coming message is data message then onMessageReceived is called even if the app is background.
But my friend told me who is sending the notification(server side), the message already goes as both display and data message. He said that data object is filled.
Following is the JSON for coming message, it has data object.
{
"to":"my_device_id",
"priority":"high",
"notification":{
"body":"Notification Body",
"title":"Notification Title",
"icon":"myicon",
"sound":"default"
},
"data":{
"Nick":"DataNick",
"Room":"DataRoom"
}
}
If I only use data object, onMessageReceived is called as they said but that time notification does not appear at the top.
Now why onMessageReceived is not called if the message is also data message. Should I do something different to handle data message? Is it working same with display messaging in client side.
Any help would be appreciated. Thanks in advance.
No way to call onMessageReceived unless the coming json includes ONLY data payload as I learned from Firebase support.
So I have to use data payload but if you use data payload it does not show notification at the top so you should create your custom notification using data payload information.
So I sent notification to myself when I get the data payload in onMessageReceived. And I set the badge in onMessageReceived right after sending notification to myself.
Following code is the final version.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
//for data payload
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
title = remoteMessage.getData().get("title");
sendNotification(remoteMessage.getData().get("body"), title);
badge = Integer.parseInt(remoteMessage.getData().get("badge"));
Log.e("notificationNUmber",":"+badge);
setBadge(getApplicationContext(), badge);
}
//for notification payload so I did not use here
// Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
}
// [END receive_message]
private void sendNotification(String messageBody, String title) {
Intent intent = new Intent(this, MainMenuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, notify_no /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
if (notify_no < 9) {
notify_no = notify_no + 1;
} else {
notify_no = 0;
}
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher_3_web)
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notify_no + 2 /* ID of notification */, notificationBuilder.build());
}
Thanks for all.
To do stuff when receiving Notification-payloads in the background, just override zzE in FirebaseMessagingService. The method name might change between versions, just type #Override into the class and see what it suggests for you. In my case, I found zzD and zzE, and by trying them out I noticed that zzE has the data I wanted. zzD had some wakelock-stuff in its extras. The return value of zzE basically indicates whether the notification was handled, so if you return true, the OS won't show the notification. I prefer to return super.zzE(intent) after I've done my things, to let the OS handle the notification.
Updating the badge will only work on launchers supported by ShortcutBadger though. You can do pretty much whatever you want instead, though.
Here's my code (my notifications contain "badge" in the data payload):
public class PushNotificationService extends FirebaseMessagingService
{
#Override
public void onMessageReceived(RemoteMessage remoteMessage)
{
// This is called when the app is in the foreground
// Show a custom notification or send a broadcast to update the UI here
}
#Override
public boolean zzE(Intent intent)
{
if(intent.hasExtra("badge"))
{
try
{
ShortcutBadger.applyCount(getApplicationContext(), Integer.parseInt(intent.getStringExtra("badge"));
}
catch (Exception e)
{
Log.e("failedToParse", "Badge!?");
}
}
// pass the intent through to the non-overriden zzE
// to show the default notification.
return super.zzE(intent);
// You could also show a custom notification here
// and return true instead of this if you
// don't want the default notifications.
}
}