Here is my manifest:
<service android:name=".fcm.PshycoFirebaseMessagingServices">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".fcm.PshycoFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
When the app is in the background and a notification arrives, then the default notification comes and doesn't run my code of onMessageReceived.
Here is my onMessageReceived code. This is invoked if my app is running on the foreground, not when it is running in the background. How can I run this code when the app is in background too?
// [START receive_message]
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
// If the application is in the foreground handle both data and notification messages here.
// 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.
data = remoteMessage.getData();
String title = remoteMessage.getNotification().getTitle();
String message = remoteMessage.getNotification().getBody();
String imageUrl = (String) data.get("image");
String action = (String) data.get("action");
Log.i(TAG, "onMessageReceived: title : "+title);
Log.i(TAG, "onMessageReceived: message : "+message);
Log.i(TAG, "onMessageReceived: imageUrl : "+imageUrl);
Log.i(TAG, "onMessageReceived: action : "+action);
if (imageUrl == null) {
sendNotification(title,message,action);
} else {
new BigPictureNotification(this,title,message,imageUrl,action);
}
}
// [END receive_message]
1. 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
NOTE: Firebase team have not developed a UI to send data-messages to
your devices, yet. You should use your server for sending this type!
2. How to?
To achieve this, you have to perform a POST request to the following URL:
POST https://fcm.googleapis.com/fcm/send
Headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key=<your-server-key>
Body using topics
{
"to": "/topics/my_topic",
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Or if you want to send it to specific devices
{
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
},
"registration_ids": ["{device-token}","{device2-token}","{device3-token}"]
}
NOTE: Be sure you're not adding JSON key notification
NOTE: To get your server key, you can find it in the firebase console: Your project -> settings -> Project settings -> Cloud messaging -> Server Key
3. How to handle the push notification message?
This is how you handle the received message:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
// Manage data
}
To make firebase library to call your onMessageReceived() in the following cases
App in foreground
App in background
App has been killed
you must not put JSON key notification in your request to Firebase API but instead, use data, see below.
The following message will not call your onMessageReceived() when your app is in the background or killed, and you can't customize your notification.
{
"to": "/topics/journal",
"notification": {
"title" : "title",
"text": "data!",
"icon": "ic_notification"
}
}
but instead using this will work
{
"to": "/topics/dev_journal",
"data": {
"text":"text",
"title":"",
"line1":"Journal",
"line2":"刊物"
}
}
Basically, the message is sent in the argument RemoteMessage along with your data object as Map<String, String>, then you can manage the notification in onMessageReceived as in the snippet here
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
//you can get your text message here.
String text= data.get("text");
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
// optional, this is to make beautiful icon
.setLargeIcon(BitmapFactory.decodeResource(
getResources(), R.mipmap.ic_launcher))
.setSmallIcon(smallIcon) //mandatory
.......
/*You can read more on notification here:
https://developer.android.com/training/notify-user/build-notification.html
https://www.youtube.com/watch?v=-iog_fmm6mE
*/
}
I feel like all the responses are incomplete but all of them have something that you need to process a notification that has data when your app is in the background.
Follow these steps and you will be able to process your notifications when your app is in the background.
Add an intent-filter like this:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
to an activity that you want to process the notification data.
Send notifications with the next format:
{
"notification" : {
"click_action" : ".MainActivity",
"body" : "new Symulti update !",
"title" : "new Symulti update !",
"icon" : "ic_notif_symulti" },
"data": { ... },
"to" : "c9Vaa3ReGdk:APA91bH-AuXgg3lDN2WMcBrNhJZoFtYF9" }
The key here is add
"click_action" : ".MainActivity"
where .MainActivity is the activity with the intent-filter that you added in step 1.
Get data info from notification in the onCreate of .MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get notification data info
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
//bundle must contain all info sent in "data" field of the notification
}
}
And that should be all you need to do.
According to the firebase documentation in send downstream using firebase, there is 2 type of payload :
data
This parameter specifies the custom key-value pairs of the message's payload.
Client app is responsible for processing data messages. Data messages have only custom key-value pairs.
notification
This parameter specifies the predefined, user-visible key-value pairs of the notification payload. FCM automatically displays the message to end-user devices on behalf of the client app. Notification messages have a predefined set of user-visible keys.
When you are in the foreground you can get the data inside FCM using onMessageReceived(), you can get your data from data payload.
data = remoteMessage.getData();
String customData = (String) data.get("customData");
When you are in background, FCM will showing notification in system tray based on the info from notification payload. Title, message, and icon which used for the notification on system tray are get from the notification payload.
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
This notification payload are used when you want to automactically showing notification on the system tray when your app is in the background.
To get notification data when your app in the background, you should add click_action inside notification payload.
If you want to open your app and perform a specific action [while backgrounded], set click_action in the notification payload and map it to an intent filter in the Activity you want to launch. For example, set click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the following:
<intent-filter>
<action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Put that intent-filter on your manifest, inside one of your activity tag. When you click the notification, it will open the app and go straight to activity that you define in click_action, in this case "OPEN_ACTIVTY_1".
And inside that activity you can get the data by :
Bundle b = getIntent().getExtras();
String someData = b.getString("someData");
I'm using FCM for my android app and use both of the payload.
Here is the example JSON i'm using :
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
Working as of July 2019
Android compileSdkVersion 28, buildToolsVersion 28.0.3 and firebase-messaging:19.0.1
After many many hours of researching through all of the other StackOverflow questions and answers, and trying innumerable outdated solutions, this solution managed to show notifications in these 3 scenarios:
- App is in foreground:
the notification is received by the onMessageReceived method at my MyFirebaseMessagingService class
- App has been killed (it is not running in background):
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is launched by calling the activity that has android.intent.category.LAUNCHER in the manifest. You can get the data part of the notification by using getIntent().getExtras() at the onCreate() method.
- App is in background:
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is brought to the foreground by launching the activity that has android.intent.category.LAUNCHER in the manifest. As my app has launchMode="singleTop" in that activity, the onCreate() method is not called because one activity of the same class is already created, instead the onNewIntent() method of that class is called and you get the data part of the notification there by using intent.getExtras().
Steps:
1- If you define your app's main activity like this:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:largeHeap="true"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name=".MainActivity" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2- add these lines at the onCreate() method of your MainActivity.class
Intent i = getIntent();
Bundle extras = i.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onCreate: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
and these methods to the same MainActivity.class:
#Override
public void onNewIntent(Intent intent){
//called when a new intent for this class is created.
// The main case is when the app was in background, a notification arrives to the tray, and the user touches the notification
super.onNewIntent(intent);
Log.d(Application.APPTAG, "onNewIntent - starting");
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onNewIntent: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
}
private void showNotificationInADialog(String title, String message) {
// show a dialog with the provided title and message
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
3- create the class MyFirebase like this:
package com.yourcompany.app;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
public MyFirebaseMessagingService() {
super();
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(Application.APPTAG, "myFirebaseMessagingService - onMessageReceived - message: " + remoteMessage);
Intent dialogIntent = new Intent(this, NotificationActivity.class);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra("msg", remoteMessage);
startActivity(dialogIntent);
}
}
4- create a new class NotificationActivity.class like this:
package com.yourcompany.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import com.google.firebase.messaging.RemoteMessage;
public class NotificationActivity extends AppCompatActivity {
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
Bundle extras = getIntent().getExtras();
Log.d(Application.APPTAG, "NotificationActivity - onCreate - extras: " + extras);
if (extras == null) {
context.finish();
return;
}
RemoteMessage msg = (RemoteMessage) extras.get("msg");
if (msg == null) {
context.finish();
return;
}
RemoteMessage.Notification notification = msg.getNotification();
if (notification == null) {
context.finish();
return;
}
String dialogMessage;
try {
dialogMessage = notification.getBody();
} catch (Exception e){
context.finish();
return;
}
String dialogTitle = notification.getTitle();
if (dialogTitle == null || dialogTitle.length() == 0) {
dialogTitle = "";
}
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.myDialog));
builder.setTitle(dialogTitle);
builder.setMessage(dialogMessage);
builder.setPositiveButton(getResources().getString(R.string.accept), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
5- Add these lines to your app Manifest, inside your tags
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="#string/default_notification_channel_id"/>
<activity android:name=".NotificationActivity"
android:theme="#style/myDialog"> </activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/notification_icon"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/color_accent" />
6- add these lines in your Application.java onCreate() method, or in MainActivity.class onCreate() method:
// notifications channel creation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String channelId = getResources().getString("default_channel_id");
String channelName = getResources().getString("General announcements");
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_LOW));
}
Done.
Now for this to work well in the 3 mentioned scenarios, you have to send the notification from the Firebase web console in the following way:
In the Notification section:
Notification Title = Title to display in the notification dialog (optional)
Notification text = Message to show to the user (required)
Then in the Target section:
App = your Android app
and in Additional Options section:
Android Notification Channel = default_channel_id
Custom Data
key: title value: (same text here than in the Title field of the Notification section)
key: body value: (same text here than in the Message field of the Notification section)
key:click_action value: .MainActivity
Sound=Disabled
Expires=4 weeks
You can debug it in the Emulator with API 28 with Google Play.
Happy coding!
According to docs
Handle messages in a backgrounded app
When your app is in the background, Android directs notification
messages to the system tray. A user tap on the notification opens the
app launcher by default.
This includes messages that contain both notification and data
payload. In these cases, the notification is delivered to the device's
system tray, and the data payload is delivered in the extras of the
intent of your launcher Activity.
If you want to open your app and perform a specific action, set
click_action in the notification payload and map it to an intent
filter in the Activity you want to launch. For example, set
click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the
following:
<intent-filter> <action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Edit :
Based on this thread :
You can't set click_action payload using Firebase Console. You could try testing with a curl command or a custom http server
curl --header "Authorization: key=<YOUR_KEY_GOES_HERE>"
--header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send
-d "{\"to\":\"/topics/news\",\"notification\":
{\"title\": \"Click Action Message\",\"text\": \"Sample message\",
\"click_action\":\"OPEN_ACTIVITY_1\"}}"
To capture the message in background you need to use a BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.legacy.content.WakefulBroadcastReceiver
import com.google.firebase.messaging.RemoteMessage
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
val TAG: String = FirebaseBroadcastReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val dataBundle = intent.extras
if (dataBundle != null)
for (key in dataBundle.keySet()) {
Log.d(TAG, "dataBundle: " + key + " : " + dataBundle.get(key))
}
val remoteMessage = RemoteMessage(dataBundle)
}
}
and add this to your manifest:
<receiver
android:name="MY_PACKAGE_NAME.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
Since the display-messages which are sent from Firebase Notification UI only works if your app is in foreground. For data-messages, there is a need to make a POST call to FCM
Steps
Install Advanced Rest Client Google Chrome Extension
Add the following headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key="your server key"
Add the body
If using topics :
{
"to" : "/topics/topic_name",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
If using registration id :
{
"registration_ids" : "[{"id"},{id1}]",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
Thats it!. Now listen to onMessageReceived callback as usual.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String value1 = data.get("key1");
String value2 = data.get("key2");
}
According to the docs: May 17, 2017
When your app is in the background, Android
directs notification messages to the system tray. A user tap on the
notification opens the app launcher by default.
This includes messages that contain both notification and data payload
(and all messages sent from the Notifications console). In these
cases, the notification is delivered to the device's system tray, and
the data payload is delivered in the extras of the intent of your
launcher Activity.
So,you should use both of the payload notification + data:
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
There is no need to use click_action.You should just get exras from intent on LAUNCHER activity
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Java code should be on onCreate method on MainActivity :
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
Bundle extras = intent.getExtras();
String someData= extras.getString("someData");
String someData2 = extras.getString("someData2");
}
You can test both of the payload notification + data from Firebase Notifications Console . Don't forget to fill custom data fields on Advanced options section
Here is more clear concepts about firebase message. I found it from their support team.
Firebase has three message types:
Notification messages : Notification message works on background or foreground. When app is in background, Notification messages are delivered to the system tray. If the app is in the foreground, messages are handled by onMessageReceived() or didReceiveRemoteNotification callbacks. These are essentially what is referred to as Display messages.
Data messages: On Android platform, data message can work on background and foreground. The data message will be handled by onMessageReceived(). A platform specific note here would be: On Android, the data payload can be retrieved in the Intent used to launch your activity. To elaborate, if you have "click_action":"launch_Activity_1", you can retrieve this intent through getIntent() from only Activity_1.
Messages with both notification and data payloads: When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification. When in the foreground, your app receives a message object with both payloads available. Secondly, the click_action parameter is often used in notification payload and not in data payload. If used inside data payload, this parameter would be treated as custom key-value pair and therefore you would need to implement custom logic for it to work as intended.
Also, I recommend you to use onMessageReceived method (see Data message) to extract the data bundle. From your logic, I checked the bundle object and haven't found expected data content. Here is a reference to a similar case which might provide more clarity.
For more info visit my this thread
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
}
is not called every time it is called only when app is in forground
there is one override method this method is called every time , no matter what app is in foreground or in background or killed but this method is available with this firebase api version
this is the version u have to import from gradle
compile 'com.google.firebase:firebase-messaging:10.2.1'
this is the method
#Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
// you can get ur data here
//intent.getExtras().get("your_data_key")
}
with previous firebase api this method was not there so in that case
fire base handle itself when app is in background .... now u have this method
what ever u want to do ... u can do it here in this method .....
if you are using previous version than default activity will start
in that case u can get data same way
if(getIntent().getExtras() != null && getIntent().getExtras().get("your_data_key") != null) {
String strNotificaiton = getIntent().getExtras().get("your_data_key").toString();
// do what ever u want ....
}
generally this is the structure from server we get in notification
{
"notification": {
"body": "Cool offers. Get them before expiring!",
"title": "Flat 80% discount",
"icon": "appicon",
"click_action": "activity name" //optional if required.....
},
"data": {
"product_id": 11,
"product_details": "details.....",
"other_info": "......."
}
}
it's up to u how u want to give that data key or u want give notification anything u can give .......
what ever u will give here with same key u will get that data .........
there are few cases if u r not sending click action in that case when u will click on notification default activity will open , but if u want to open your specific activity when app is in background u can call your activity from this on handleIntent method because this is called every time
Simple summary like this
if your app is running;
onMessageReceived()
is triggers.
if your app is not running (killed by swiping) ;
onMessageReceived()
is not triggered and delivered by direclty. If you have any specialy key-value pair. They don' t work beacuse of onMessageReceived() not working.
I' ve found this way;
In your launcher activity, put this logic,
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.activity_splash);
if (getIntent().getExtras() != null && getIntent().getExtras().containsKey("PACKAGE_NAME")) {
// do what you want
// and this for killing app if we dont want to start
android.os.Process.killProcess(android.os.Process.myPid());
} else {
//continue to app
}
}
in this if block, search for your keys according to firebase UI.
In this example my key and value like above; (sorry for language =))
When my code works, i get "com.rda.note".
android.os.Process.killProcess(android.os.Process.myPid());
with this line of code, i closed my application and open Google Play Market
happy coding =)
2017 updated answer
Here is a clear-cut answer from the docs regarding this:
I figured out the scenarios,
When app is in foreground,
onMessageReceived() method is called from the FirebaseService.So the pendingIntent defined in the service class will be called.
And when app is in background, first activity is called.
Now, if you use a splash activity, then must keep in mind the splashactivity will be called, else if there is no splashActivity, then whatever the first activity is, will be called.
Then you need to check getIntent() of the firstActivity to see if it has any bundle .if everything is alright you will see bundle is there with values filled in. If the value in data tag sent from server looks like this,
"data": {
"user_name": "arefin sajib",
"value": "user name notification"
}
Then in the first activity, you will see,
there is a valid intent( getIntent() is not null) , valid bundle and inside bundle , there will the whole JSON mentioned above with data as key.
For this scenario, code for extraction of value will look like this,
if(getIntent()!=null){
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
try {
JSONObject object = new JSONObject(bundle.getStringExtra("data"));
String user_name = object.optString("user_name");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
I had same problem. After some digging why my MainActivity is called with intent without data I realized that my LAUNCHER activity (as in Manifest) is SplashActivity. There I found the message data and forwarded them to MainActivity. Works like sharm. I beleive this can help someone.
Thanks for all another answers.
Remove notification payload completely from your server request. Send only data and handle it in onMessageReceived(), otherwise your onMessageReceived will not be triggered when the app is in background or killed.
Here is what I am sending from server:
{
"data":{
"id": 1,
"missedRequests": 5
"addAnyDataHere": 123
},
"to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......"
}
So you can receive your data in onMessageReceived(RemoteMessage message) like this: (let's say I have to get the id)
Object obj = message.getData().get("id");
if (obj != null) {
int id = Integer.valueOf(obj.toString());
}
And similarly you can get any data which you have sent from server within onMessageReceived().
Thanks to All of you for your Answers. But I solved this by sending data message instead of sending Notification.
Server code
<?php
$url = "https://fcm.googleapis.com/fcm/send";
$token = "C-l6T_a7HouUK****";
$serverKey = "AAAAaOcKS00:********";
define( 'API_ACCESS_KEY', $serverKey );
$registrationIds = array($token);
// prep the bundle
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'subtitle' => 'This is a subtitle. subtitle',
'tickerText' => 'Ticker text here...Ticker text here...Ticker text
here',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send'
);
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
?>
And caught the Data in onMessageReceived
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("message"));
}
// Check if message contains a notification payload.
else if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
sendNotification(remoteMessage.getNotification().getBody());
}
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, Notify.class).putExtra("msg",messageBody);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = "idddd";
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(MyFirebaseMessagingService.this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("FCM Message")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
In general
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
Data Messages example:
{
"to": "/path",
"data":
{
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Display Messages example:
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
Android side can handle notifications like:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
…
#Override public void onMessageReceived(RemoteMessage remoteMessage){
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
}
…
}
More details about FCM you can find here : Set up a Firebase Cloud Messaging client app on Android
I added the following code in the firebase-messaging-sw.js,
messaging.onBackgroundmessage((payload)=>{
console.log("background message detected!!");
console.log("message : ", payload);
})
this was triggered every time message received in background. but I was unable to use the payload in the main thread as SW didn't supported it. so then I researched a lot and found a solution in an Android forum.
so the solution is that we will have to remove the notification payload from the request payload.
so I changed my payload from
{
"notification": {
"title": "Hey there",
"body": "Subscribe to AMAL MOHAN N youtube channel"
},
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
to
{
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
change in the payload automatically made the receiveMessage() trigger in foreground messages as well as background messages.
I found this in an Android forum and this worked for me! please let me know if this works for you.
The easy way to send messages even if the app is in background and foreground as follow:-
To send a message using API, you can use a tool called AdvancedREST Client, its a chrome extension, and send a message with the following parameters.
Rest client tool Link: https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo
use this url:- https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=Your Server key From or Authoization key(see below ref)
{ "data": {
"image": "https://static.pexels.com/photos/4825/red-love-romantic-flowers.jpg",
"message": "Firebase Push Message Using API"
"AnotherActivity": "True"
},
"to" : "device id Or Device token"
}
Authorization key can be obtained by visiting Google developers console and click on Credentials button on the left menu for your project. Among the API keys listed, the server key will be your authorization key.
And you need to put tokenID of the receiver in the “to” section of your POST request sent using API.
you want to work onMessageReceived(RemoteMessage remoteMessage) in background send only data part notification part this:
"data": "image": "", "message": "Firebase Push Message Using API",
"AnotherActivity": "True", "to" : "device id Or Device token"
By this onMessageRecivied is call background and foreground no need to handle notification using notification tray on your launcher activity.
Handle data payload in using this:
public void onMessageReceived(RemoteMessage remoteMessage)
if (remoteMessage.getData().size() > 0)
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
The solutions provided didn't work in my case.
At the end of the day, I figured out that some battery optimization apps give users the control to allow or block apps from sending notifications. In my case it was Samsung's Smart Manager that was automatically blocking my app as soon as it was killed/cleaned from the recents list.
Turning off that feature for my app was the only solution I could find.
To be able to retrieve data from firebase notification sent when app is in background, you need to add click_action entry in your notification data set.
Set additional options of your notification in firebase console like this: (you have to include any extra data that you want to retrieve in your app here):
And include the intent filter in your manifest file under the activity to be launched
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.MyApp.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<intent-filter>
<action android:name="FIREBASE_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Then get the bundle data in your activity onNewIntent:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle data = intent.getExtras();
if (data != null) {
for (String key : data.keySet()) {
Object value = data.get(key);
// do what you want with the data entries
Log.d(FIREBASE_TAG, "Key: " + key + " Value: " + value);
Toast.makeText(this, "Key: "+key+".... Value: "+value, Toast.LENGTH_LONG).show;
}
}
}
When your app is in foreground, you can set your onMessageReceived like this:
#Override
public void onMessageReceived(#NonNull RemoteMessage message) {
Log.d(FIREBASE_TAG, "Message From: " + message.getFrom());
if (message.getNotification() != null) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Map<String, String> data = message.getData();
if(data != null && !data.isEmpty()){
for(Map.Entry<String ,String > entry : data.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
}
//.......
// implement the rest of the code to show notification
//
}
}
June 2018 Answer -
You have to make sure there is not a "notification" keyword anywhere in the message. Only include "data", and the app will be able to handle the message in onMessageReceived, even if in background or killed.
Using Cloud Functions:
const message = {
token: token_id, // obtain device token id by querying data in firebase
data: {
title: "my_custom_title",
body: "my_custom_body_message"
}
}
return admin.messaging().send(message).then(response => {
// handle response
});
Then in your onMessageReceived(), in your class extending com.google.firebase.messaging.FirebaseMessagingService :
if (data != null) {
Log.d(TAG, "data title is: " + data.get("title");
Log.d(TAG, "data body is: " + data.get("body");
}
// build notification using the body, title, and whatever else you want.
According to OAUTH 2.0:
There will be Auth problem for this case beacuse FCM now using OAUTH 2
So I read firebase documentation and according to documentation new way to post data message is;
POST: https://fcm.googleapis.com/v1/projects/YOUR_FIREBASEDB_ID/messages:send
Headers
Key: Content-Type, Value: application/json
Auth
Bearer YOUR_TOKEN
Example Body
{
"message":{
"topic" : "xxx",
"data" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
In the url there is Database Id which you can find it on your firebase console. (Go project setttings)
And now lets take our token (It will valid only 1 hr):
First in the Firebase console, open Settings > Service Accounts. Click Generate New Private Key, securely store the JSON file containing the key. I was need this JSON file to authorize server requests manually. I downloaded it.
Then I create a node.js project and used this function to get my token;
var PROJECT_ID = 'YOUR_PROJECT_ID';
var HOST = 'fcm.googleapis.com';
var PATH = '/v1/projects/' + PROJECT_ID + '/messages:send';
var MESSAGING_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging';
var SCOPES = [MESSAGING_SCOPE];
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
getAccessToken().then(function(accessToken) {
console.log("TOKEN: "+accessToken)
})
});
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./YOUR_DOWNLOADED_JSON_FILE.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
Now I can use this token in my post request. Then I post my data message, and it is now handled by my apps onMessageReceived function.
Since 2019, Google Firebase has a big change in their APIs
I mean:
'com.google.firebase:firebase-messaging:18.0.0'
in 18.0.0 they removed MyFirebaseInstanceIDService and you need to get token in MyFirebaseMessagingService so you just need to write :
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
}
and also in your AndroidManifest.xml, you have to remove :
<service android:name=".service.MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Also, you're recommended to set default values to customize the appearance of notifications. You can specify a custom default icon and a custom default color that are applied whenever equivalent values are not set in the notification payload.
Add these lines inside the application tag to set the custom default icon and custom color:
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorAccent" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/push_channel" />
now to handle notification messages in a backgrounded app you should define an Intent in your first Activity even if it is SplashScreen, When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.
for example, if your Json is like this:
"data": {
"message": "2",
"title": "1",
"pushType" : "banner",
"bannerLink": "http://www.google.com",
"image" : "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"}
you just need to write a simple intent to get those values:
Bundle extras = intent.getExtras();
String bannerLink = extras.getString("bannerLink");
...
String channelId = extras.getString("channelId");
In addition to above answers,
If you are testing push notifications using FCM console, 'data' key and object is not added to Push Notification bundle. So you will not receive detailed push notification when App is background or killed.
In this case you have to opt for your back end admin console to test App background scenario.
Here, you will have added 'data' key to your push bundle. so, detailed push will be shown as expected.
Hope this helps few.
Using this code you can get the notification in background/foreground and also put action:
//Data should come in this format from the notification
{
"to": "/xyz/Notifications",
"data": {
"key1": "title notification",
"key2": "description notification"
}
}
In-App use this code:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String key1Data = remoteMessage.getData().get("key1");
// use key1Data to according to your need
}
I solved this issue by using Broadcast Message.
create an Broadcast and send the payload in broadcast Message from the service worker.
then Receive the payload inside application and handle it the way you want.
This case is only for firebase admin notification
If the application is in the background FirebaseMessagingService does not get triggered
For handling this case go to launcher activity and check for the intent bundle which is attached, and print out all data using this code :
intent?.extras?.let {it->
for (key in bundle.keySet()) {
val value = bundle[key]
Log.d("NotificationData", String.format("%s %s (%s)", key, value.toString(), value!!.javaClass.name))
}
}
check-in log all data with data types is displayed ;
Example :
I want to get the Week and title from notifications will use this code
intent?.extras?.let {it->
if (it.containsKey("week")){
}
if (it.containsKey("title")){
}
Related
Here is my manifest:
<service android:name=".fcm.PshycoFirebaseMessagingServices">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".fcm.PshycoFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
When the app is in the background and a notification arrives, then the default notification comes and doesn't run my code of onMessageReceived.
Here is my onMessageReceived code. This is invoked if my app is running on the foreground, not when it is running in the background. How can I run this code when the app is in background too?
// [START receive_message]
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
// If the application is in the foreground handle both data and notification messages here.
// 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.
data = remoteMessage.getData();
String title = remoteMessage.getNotification().getTitle();
String message = remoteMessage.getNotification().getBody();
String imageUrl = (String) data.get("image");
String action = (String) data.get("action");
Log.i(TAG, "onMessageReceived: title : "+title);
Log.i(TAG, "onMessageReceived: message : "+message);
Log.i(TAG, "onMessageReceived: imageUrl : "+imageUrl);
Log.i(TAG, "onMessageReceived: action : "+action);
if (imageUrl == null) {
sendNotification(title,message,action);
} else {
new BigPictureNotification(this,title,message,imageUrl,action);
}
}
// [END receive_message]
1. 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
NOTE: Firebase team have not developed a UI to send data-messages to
your devices, yet. You should use your server for sending this type!
2. How to?
To achieve this, you have to perform a POST request to the following URL:
POST https://fcm.googleapis.com/fcm/send
Headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key=<your-server-key>
Body using topics
{
"to": "/topics/my_topic",
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Or if you want to send it to specific devices
{
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
},
"registration_ids": ["{device-token}","{device2-token}","{device3-token}"]
}
NOTE: Be sure you're not adding JSON key notification
NOTE: To get your server key, you can find it in the firebase console: Your project -> settings -> Project settings -> Cloud messaging -> Server Key
3. How to handle the push notification message?
This is how you handle the received message:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
// Manage data
}
To make firebase library to call your onMessageReceived() in the following cases
App in foreground
App in background
App has been killed
you must not put JSON key notification in your request to Firebase API but instead, use data, see below.
The following message will not call your onMessageReceived() when your app is in the background or killed, and you can't customize your notification.
{
"to": "/topics/journal",
"notification": {
"title" : "title",
"text": "data!",
"icon": "ic_notification"
}
}
but instead using this will work
{
"to": "/topics/dev_journal",
"data": {
"text":"text",
"title":"",
"line1":"Journal",
"line2":"刊物"
}
}
Basically, the message is sent in the argument RemoteMessage along with your data object as Map<String, String>, then you can manage the notification in onMessageReceived as in the snippet here
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
//you can get your text message here.
String text= data.get("text");
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
// optional, this is to make beautiful icon
.setLargeIcon(BitmapFactory.decodeResource(
getResources(), R.mipmap.ic_launcher))
.setSmallIcon(smallIcon) //mandatory
.......
/*You can read more on notification here:
https://developer.android.com/training/notify-user/build-notification.html
https://www.youtube.com/watch?v=-iog_fmm6mE
*/
}
I feel like all the responses are incomplete but all of them have something that you need to process a notification that has data when your app is in the background.
Follow these steps and you will be able to process your notifications when your app is in the background.
Add an intent-filter like this:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
to an activity that you want to process the notification data.
Send notifications with the next format:
{
"notification" : {
"click_action" : ".MainActivity",
"body" : "new Symulti update !",
"title" : "new Symulti update !",
"icon" : "ic_notif_symulti" },
"data": { ... },
"to" : "c9Vaa3ReGdk:APA91bH-AuXgg3lDN2WMcBrNhJZoFtYF9" }
The key here is add
"click_action" : ".MainActivity"
where .MainActivity is the activity with the intent-filter that you added in step 1.
Get data info from notification in the onCreate of .MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get notification data info
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
//bundle must contain all info sent in "data" field of the notification
}
}
And that should be all you need to do.
According to the firebase documentation in send downstream using firebase, there is 2 type of payload :
data
This parameter specifies the custom key-value pairs of the message's payload.
Client app is responsible for processing data messages. Data messages have only custom key-value pairs.
notification
This parameter specifies the predefined, user-visible key-value pairs of the notification payload. FCM automatically displays the message to end-user devices on behalf of the client app. Notification messages have a predefined set of user-visible keys.
When you are in the foreground you can get the data inside FCM using onMessageReceived(), you can get your data from data payload.
data = remoteMessage.getData();
String customData = (String) data.get("customData");
When you are in background, FCM will showing notification in system tray based on the info from notification payload. Title, message, and icon which used for the notification on system tray are get from the notification payload.
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
This notification payload are used when you want to automactically showing notification on the system tray when your app is in the background.
To get notification data when your app in the background, you should add click_action inside notification payload.
If you want to open your app and perform a specific action [while backgrounded], set click_action in the notification payload and map it to an intent filter in the Activity you want to launch. For example, set click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the following:
<intent-filter>
<action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Put that intent-filter on your manifest, inside one of your activity tag. When you click the notification, it will open the app and go straight to activity that you define in click_action, in this case "OPEN_ACTIVTY_1".
And inside that activity you can get the data by :
Bundle b = getIntent().getExtras();
String someData = b.getString("someData");
I'm using FCM for my android app and use both of the payload.
Here is the example JSON i'm using :
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
Working as of July 2019
Android compileSdkVersion 28, buildToolsVersion 28.0.3 and firebase-messaging:19.0.1
After many many hours of researching through all of the other StackOverflow questions and answers, and trying innumerable outdated solutions, this solution managed to show notifications in these 3 scenarios:
- App is in foreground:
the notification is received by the onMessageReceived method at my MyFirebaseMessagingService class
- App has been killed (it is not running in background):
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is launched by calling the activity that has android.intent.category.LAUNCHER in the manifest. You can get the data part of the notification by using getIntent().getExtras() at the onCreate() method.
- App is in background:
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is brought to the foreground by launching the activity that has android.intent.category.LAUNCHER in the manifest. As my app has launchMode="singleTop" in that activity, the onCreate() method is not called because one activity of the same class is already created, instead the onNewIntent() method of that class is called and you get the data part of the notification there by using intent.getExtras().
Steps:
1- If you define your app's main activity like this:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:largeHeap="true"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name=".MainActivity" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2- add these lines at the onCreate() method of your MainActivity.class
Intent i = getIntent();
Bundle extras = i.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onCreate: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
and these methods to the same MainActivity.class:
#Override
public void onNewIntent(Intent intent){
//called when a new intent for this class is created.
// The main case is when the app was in background, a notification arrives to the tray, and the user touches the notification
super.onNewIntent(intent);
Log.d(Application.APPTAG, "onNewIntent - starting");
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onNewIntent: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
}
private void showNotificationInADialog(String title, String message) {
// show a dialog with the provided title and message
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
3- create the class MyFirebase like this:
package com.yourcompany.app;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
public MyFirebaseMessagingService() {
super();
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(Application.APPTAG, "myFirebaseMessagingService - onMessageReceived - message: " + remoteMessage);
Intent dialogIntent = new Intent(this, NotificationActivity.class);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra("msg", remoteMessage);
startActivity(dialogIntent);
}
}
4- create a new class NotificationActivity.class like this:
package com.yourcompany.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import com.google.firebase.messaging.RemoteMessage;
public class NotificationActivity extends AppCompatActivity {
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
Bundle extras = getIntent().getExtras();
Log.d(Application.APPTAG, "NotificationActivity - onCreate - extras: " + extras);
if (extras == null) {
context.finish();
return;
}
RemoteMessage msg = (RemoteMessage) extras.get("msg");
if (msg == null) {
context.finish();
return;
}
RemoteMessage.Notification notification = msg.getNotification();
if (notification == null) {
context.finish();
return;
}
String dialogMessage;
try {
dialogMessage = notification.getBody();
} catch (Exception e){
context.finish();
return;
}
String dialogTitle = notification.getTitle();
if (dialogTitle == null || dialogTitle.length() == 0) {
dialogTitle = "";
}
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.myDialog));
builder.setTitle(dialogTitle);
builder.setMessage(dialogMessage);
builder.setPositiveButton(getResources().getString(R.string.accept), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
5- Add these lines to your app Manifest, inside your tags
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="#string/default_notification_channel_id"/>
<activity android:name=".NotificationActivity"
android:theme="#style/myDialog"> </activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/notification_icon"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/color_accent" />
6- add these lines in your Application.java onCreate() method, or in MainActivity.class onCreate() method:
// notifications channel creation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String channelId = getResources().getString("default_channel_id");
String channelName = getResources().getString("General announcements");
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_LOW));
}
Done.
Now for this to work well in the 3 mentioned scenarios, you have to send the notification from the Firebase web console in the following way:
In the Notification section:
Notification Title = Title to display in the notification dialog (optional)
Notification text = Message to show to the user (required)
Then in the Target section:
App = your Android app
and in Additional Options section:
Android Notification Channel = default_channel_id
Custom Data
key: title value: (same text here than in the Title field of the Notification section)
key: body value: (same text here than in the Message field of the Notification section)
key:click_action value: .MainActivity
Sound=Disabled
Expires=4 weeks
You can debug it in the Emulator with API 28 with Google Play.
Happy coding!
According to docs
Handle messages in a backgrounded app
When your app is in the background, Android directs notification
messages to the system tray. A user tap on the notification opens the
app launcher by default.
This includes messages that contain both notification and data
payload. In these cases, the notification is delivered to the device's
system tray, and the data payload is delivered in the extras of the
intent of your launcher Activity.
If you want to open your app and perform a specific action, set
click_action in the notification payload and map it to an intent
filter in the Activity you want to launch. For example, set
click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the
following:
<intent-filter> <action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Edit :
Based on this thread :
You can't set click_action payload using Firebase Console. You could try testing with a curl command or a custom http server
curl --header "Authorization: key=<YOUR_KEY_GOES_HERE>"
--header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send
-d "{\"to\":\"/topics/news\",\"notification\":
{\"title\": \"Click Action Message\",\"text\": \"Sample message\",
\"click_action\":\"OPEN_ACTIVITY_1\"}}"
To capture the message in background you need to use a BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.legacy.content.WakefulBroadcastReceiver
import com.google.firebase.messaging.RemoteMessage
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
val TAG: String = FirebaseBroadcastReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val dataBundle = intent.extras
if (dataBundle != null)
for (key in dataBundle.keySet()) {
Log.d(TAG, "dataBundle: " + key + " : " + dataBundle.get(key))
}
val remoteMessage = RemoteMessage(dataBundle)
}
}
and add this to your manifest:
<receiver
android:name="MY_PACKAGE_NAME.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
Since the display-messages which are sent from Firebase Notification UI only works if your app is in foreground. For data-messages, there is a need to make a POST call to FCM
Steps
Install Advanced Rest Client Google Chrome Extension
Add the following headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key="your server key"
Add the body
If using topics :
{
"to" : "/topics/topic_name",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
If using registration id :
{
"registration_ids" : "[{"id"},{id1}]",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
Thats it!. Now listen to onMessageReceived callback as usual.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String value1 = data.get("key1");
String value2 = data.get("key2");
}
According to the docs: May 17, 2017
When your app is in the background, Android
directs notification messages to the system tray. A user tap on the
notification opens the app launcher by default.
This includes messages that contain both notification and data payload
(and all messages sent from the Notifications console). In these
cases, the notification is delivered to the device's system tray, and
the data payload is delivered in the extras of the intent of your
launcher Activity.
So,you should use both of the payload notification + data:
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
There is no need to use click_action.You should just get exras from intent on LAUNCHER activity
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Java code should be on onCreate method on MainActivity :
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
Bundle extras = intent.getExtras();
String someData= extras.getString("someData");
String someData2 = extras.getString("someData2");
}
You can test both of the payload notification + data from Firebase Notifications Console . Don't forget to fill custom data fields on Advanced options section
Here is more clear concepts about firebase message. I found it from their support team.
Firebase has three message types:
Notification messages : Notification message works on background or foreground. When app is in background, Notification messages are delivered to the system tray. If the app is in the foreground, messages are handled by onMessageReceived() or didReceiveRemoteNotification callbacks. These are essentially what is referred to as Display messages.
Data messages: On Android platform, data message can work on background and foreground. The data message will be handled by onMessageReceived(). A platform specific note here would be: On Android, the data payload can be retrieved in the Intent used to launch your activity. To elaborate, if you have "click_action":"launch_Activity_1", you can retrieve this intent through getIntent() from only Activity_1.
Messages with both notification and data payloads: When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification. When in the foreground, your app receives a message object with both payloads available. Secondly, the click_action parameter is often used in notification payload and not in data payload. If used inside data payload, this parameter would be treated as custom key-value pair and therefore you would need to implement custom logic for it to work as intended.
Also, I recommend you to use onMessageReceived method (see Data message) to extract the data bundle. From your logic, I checked the bundle object and haven't found expected data content. Here is a reference to a similar case which might provide more clarity.
For more info visit my this thread
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
}
is not called every time it is called only when app is in forground
there is one override method this method is called every time , no matter what app is in foreground or in background or killed but this method is available with this firebase api version
this is the version u have to import from gradle
compile 'com.google.firebase:firebase-messaging:10.2.1'
this is the method
#Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
// you can get ur data here
//intent.getExtras().get("your_data_key")
}
with previous firebase api this method was not there so in that case
fire base handle itself when app is in background .... now u have this method
what ever u want to do ... u can do it here in this method .....
if you are using previous version than default activity will start
in that case u can get data same way
if(getIntent().getExtras() != null && getIntent().getExtras().get("your_data_key") != null) {
String strNotificaiton = getIntent().getExtras().get("your_data_key").toString();
// do what ever u want ....
}
generally this is the structure from server we get in notification
{
"notification": {
"body": "Cool offers. Get them before expiring!",
"title": "Flat 80% discount",
"icon": "appicon",
"click_action": "activity name" //optional if required.....
},
"data": {
"product_id": 11,
"product_details": "details.....",
"other_info": "......."
}
}
it's up to u how u want to give that data key or u want give notification anything u can give .......
what ever u will give here with same key u will get that data .........
there are few cases if u r not sending click action in that case when u will click on notification default activity will open , but if u want to open your specific activity when app is in background u can call your activity from this on handleIntent method because this is called every time
Simple summary like this
if your app is running;
onMessageReceived()
is triggers.
if your app is not running (killed by swiping) ;
onMessageReceived()
is not triggered and delivered by direclty. If you have any specialy key-value pair. They don' t work beacuse of onMessageReceived() not working.
I' ve found this way;
In your launcher activity, put this logic,
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.activity_splash);
if (getIntent().getExtras() != null && getIntent().getExtras().containsKey("PACKAGE_NAME")) {
// do what you want
// and this for killing app if we dont want to start
android.os.Process.killProcess(android.os.Process.myPid());
} else {
//continue to app
}
}
in this if block, search for your keys according to firebase UI.
In this example my key and value like above; (sorry for language =))
When my code works, i get "com.rda.note".
android.os.Process.killProcess(android.os.Process.myPid());
with this line of code, i closed my application and open Google Play Market
happy coding =)
2017 updated answer
Here is a clear-cut answer from the docs regarding this:
I figured out the scenarios,
When app is in foreground,
onMessageReceived() method is called from the FirebaseService.So the pendingIntent defined in the service class will be called.
And when app is in background, first activity is called.
Now, if you use a splash activity, then must keep in mind the splashactivity will be called, else if there is no splashActivity, then whatever the first activity is, will be called.
Then you need to check getIntent() of the firstActivity to see if it has any bundle .if everything is alright you will see bundle is there with values filled in. If the value in data tag sent from server looks like this,
"data": {
"user_name": "arefin sajib",
"value": "user name notification"
}
Then in the first activity, you will see,
there is a valid intent( getIntent() is not null) , valid bundle and inside bundle , there will the whole JSON mentioned above with data as key.
For this scenario, code for extraction of value will look like this,
if(getIntent()!=null){
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
try {
JSONObject object = new JSONObject(bundle.getStringExtra("data"));
String user_name = object.optString("user_name");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
I had same problem. After some digging why my MainActivity is called with intent without data I realized that my LAUNCHER activity (as in Manifest) is SplashActivity. There I found the message data and forwarded them to MainActivity. Works like sharm. I beleive this can help someone.
Thanks for all another answers.
Remove notification payload completely from your server request. Send only data and handle it in onMessageReceived(), otherwise your onMessageReceived will not be triggered when the app is in background or killed.
Here is what I am sending from server:
{
"data":{
"id": 1,
"missedRequests": 5
"addAnyDataHere": 123
},
"to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......"
}
So you can receive your data in onMessageReceived(RemoteMessage message) like this: (let's say I have to get the id)
Object obj = message.getData().get("id");
if (obj != null) {
int id = Integer.valueOf(obj.toString());
}
And similarly you can get any data which you have sent from server within onMessageReceived().
Thanks to All of you for your Answers. But I solved this by sending data message instead of sending Notification.
Server code
<?php
$url = "https://fcm.googleapis.com/fcm/send";
$token = "C-l6T_a7HouUK****";
$serverKey = "AAAAaOcKS00:********";
define( 'API_ACCESS_KEY', $serverKey );
$registrationIds = array($token);
// prep the bundle
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'subtitle' => 'This is a subtitle. subtitle',
'tickerText' => 'Ticker text here...Ticker text here...Ticker text
here',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send'
);
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
?>
And caught the Data in onMessageReceived
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("message"));
}
// Check if message contains a notification payload.
else if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
sendNotification(remoteMessage.getNotification().getBody());
}
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, Notify.class).putExtra("msg",messageBody);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = "idddd";
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(MyFirebaseMessagingService.this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("FCM Message")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
In general
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
Data Messages example:
{
"to": "/path",
"data":
{
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Display Messages example:
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
Android side can handle notifications like:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
…
#Override public void onMessageReceived(RemoteMessage remoteMessage){
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
}
…
}
More details about FCM you can find here : Set up a Firebase Cloud Messaging client app on Android
I added the following code in the firebase-messaging-sw.js,
messaging.onBackgroundmessage((payload)=>{
console.log("background message detected!!");
console.log("message : ", payload);
})
this was triggered every time message received in background. but I was unable to use the payload in the main thread as SW didn't supported it. so then I researched a lot and found a solution in an Android forum.
so the solution is that we will have to remove the notification payload from the request payload.
so I changed my payload from
{
"notification": {
"title": "Hey there",
"body": "Subscribe to AMAL MOHAN N youtube channel"
},
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
to
{
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
change in the payload automatically made the receiveMessage() trigger in foreground messages as well as background messages.
I found this in an Android forum and this worked for me! please let me know if this works for you.
The easy way to send messages even if the app is in background and foreground as follow:-
To send a message using API, you can use a tool called AdvancedREST Client, its a chrome extension, and send a message with the following parameters.
Rest client tool Link: https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo
use this url:- https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=Your Server key From or Authoization key(see below ref)
{ "data": {
"image": "https://static.pexels.com/photos/4825/red-love-romantic-flowers.jpg",
"message": "Firebase Push Message Using API"
"AnotherActivity": "True"
},
"to" : "device id Or Device token"
}
Authorization key can be obtained by visiting Google developers console and click on Credentials button on the left menu for your project. Among the API keys listed, the server key will be your authorization key.
And you need to put tokenID of the receiver in the “to” section of your POST request sent using API.
you want to work onMessageReceived(RemoteMessage remoteMessage) in background send only data part notification part this:
"data": "image": "", "message": "Firebase Push Message Using API",
"AnotherActivity": "True", "to" : "device id Or Device token"
By this onMessageRecivied is call background and foreground no need to handle notification using notification tray on your launcher activity.
Handle data payload in using this:
public void onMessageReceived(RemoteMessage remoteMessage)
if (remoteMessage.getData().size() > 0)
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
The solutions provided didn't work in my case.
At the end of the day, I figured out that some battery optimization apps give users the control to allow or block apps from sending notifications. In my case it was Samsung's Smart Manager that was automatically blocking my app as soon as it was killed/cleaned from the recents list.
Turning off that feature for my app was the only solution I could find.
To be able to retrieve data from firebase notification sent when app is in background, you need to add click_action entry in your notification data set.
Set additional options of your notification in firebase console like this: (you have to include any extra data that you want to retrieve in your app here):
And include the intent filter in your manifest file under the activity to be launched
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.MyApp.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<intent-filter>
<action android:name="FIREBASE_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Then get the bundle data in your activity onNewIntent:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle data = intent.getExtras();
if (data != null) {
for (String key : data.keySet()) {
Object value = data.get(key);
// do what you want with the data entries
Log.d(FIREBASE_TAG, "Key: " + key + " Value: " + value);
Toast.makeText(this, "Key: "+key+".... Value: "+value, Toast.LENGTH_LONG).show;
}
}
}
When your app is in foreground, you can set your onMessageReceived like this:
#Override
public void onMessageReceived(#NonNull RemoteMessage message) {
Log.d(FIREBASE_TAG, "Message From: " + message.getFrom());
if (message.getNotification() != null) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Map<String, String> data = message.getData();
if(data != null && !data.isEmpty()){
for(Map.Entry<String ,String > entry : data.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
}
//.......
// implement the rest of the code to show notification
//
}
}
June 2018 Answer -
You have to make sure there is not a "notification" keyword anywhere in the message. Only include "data", and the app will be able to handle the message in onMessageReceived, even if in background or killed.
Using Cloud Functions:
const message = {
token: token_id, // obtain device token id by querying data in firebase
data: {
title: "my_custom_title",
body: "my_custom_body_message"
}
}
return admin.messaging().send(message).then(response => {
// handle response
});
Then in your onMessageReceived(), in your class extending com.google.firebase.messaging.FirebaseMessagingService :
if (data != null) {
Log.d(TAG, "data title is: " + data.get("title");
Log.d(TAG, "data body is: " + data.get("body");
}
// build notification using the body, title, and whatever else you want.
According to OAUTH 2.0:
There will be Auth problem for this case beacuse FCM now using OAUTH 2
So I read firebase documentation and according to documentation new way to post data message is;
POST: https://fcm.googleapis.com/v1/projects/YOUR_FIREBASEDB_ID/messages:send
Headers
Key: Content-Type, Value: application/json
Auth
Bearer YOUR_TOKEN
Example Body
{
"message":{
"topic" : "xxx",
"data" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
In the url there is Database Id which you can find it on your firebase console. (Go project setttings)
And now lets take our token (It will valid only 1 hr):
First in the Firebase console, open Settings > Service Accounts. Click Generate New Private Key, securely store the JSON file containing the key. I was need this JSON file to authorize server requests manually. I downloaded it.
Then I create a node.js project and used this function to get my token;
var PROJECT_ID = 'YOUR_PROJECT_ID';
var HOST = 'fcm.googleapis.com';
var PATH = '/v1/projects/' + PROJECT_ID + '/messages:send';
var MESSAGING_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging';
var SCOPES = [MESSAGING_SCOPE];
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
getAccessToken().then(function(accessToken) {
console.log("TOKEN: "+accessToken)
})
});
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./YOUR_DOWNLOADED_JSON_FILE.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
Now I can use this token in my post request. Then I post my data message, and it is now handled by my apps onMessageReceived function.
Since 2019, Google Firebase has a big change in their APIs
I mean:
'com.google.firebase:firebase-messaging:18.0.0'
in 18.0.0 they removed MyFirebaseInstanceIDService and you need to get token in MyFirebaseMessagingService so you just need to write :
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
}
and also in your AndroidManifest.xml, you have to remove :
<service android:name=".service.MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Also, you're recommended to set default values to customize the appearance of notifications. You can specify a custom default icon and a custom default color that are applied whenever equivalent values are not set in the notification payload.
Add these lines inside the application tag to set the custom default icon and custom color:
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorAccent" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/push_channel" />
now to handle notification messages in a backgrounded app you should define an Intent in your first Activity even if it is SplashScreen, When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.
for example, if your Json is like this:
"data": {
"message": "2",
"title": "1",
"pushType" : "banner",
"bannerLink": "http://www.google.com",
"image" : "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"}
you just need to write a simple intent to get those values:
Bundle extras = intent.getExtras();
String bannerLink = extras.getString("bannerLink");
...
String channelId = extras.getString("channelId");
In addition to above answers,
If you are testing push notifications using FCM console, 'data' key and object is not added to Push Notification bundle. So you will not receive detailed push notification when App is background or killed.
In this case you have to opt for your back end admin console to test App background scenario.
Here, you will have added 'data' key to your push bundle. so, detailed push will be shown as expected.
Hope this helps few.
Using this code you can get the notification in background/foreground and also put action:
//Data should come in this format from the notification
{
"to": "/xyz/Notifications",
"data": {
"key1": "title notification",
"key2": "description notification"
}
}
In-App use this code:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String key1Data = remoteMessage.getData().get("key1");
// use key1Data to according to your need
}
I solved this issue by using Broadcast Message.
create an Broadcast and send the payload in broadcast Message from the service worker.
then Receive the payload inside application and handle it the way you want.
This case is only for firebase admin notification
If the application is in the background FirebaseMessagingService does not get triggered
For handling this case go to launcher activity and check for the intent bundle which is attached, and print out all data using this code :
intent?.extras?.let {it->
for (key in bundle.keySet()) {
val value = bundle[key]
Log.d("NotificationData", String.format("%s %s (%s)", key, value.toString(), value!!.javaClass.name))
}
}
check-in log all data with data types is displayed ;
Example :
I want to get the Week and title from notifications will use this code
intent?.extras?.let {it->
if (it.containsKey("week")){
}
if (it.containsKey("title")){
}
Here is my manifest:
<service android:name=".fcm.PshycoFirebaseMessagingServices">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".fcm.PshycoFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
When the app is in the background and a notification arrives, then the default notification comes and doesn't run my code of onMessageReceived.
Here is my onMessageReceived code. This is invoked if my app is running on the foreground, not when it is running in the background. How can I run this code when the app is in background too?
// [START receive_message]
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
// If the application is in the foreground handle both data and notification messages here.
// 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.
data = remoteMessage.getData();
String title = remoteMessage.getNotification().getTitle();
String message = remoteMessage.getNotification().getBody();
String imageUrl = (String) data.get("image");
String action = (String) data.get("action");
Log.i(TAG, "onMessageReceived: title : "+title);
Log.i(TAG, "onMessageReceived: message : "+message);
Log.i(TAG, "onMessageReceived: imageUrl : "+imageUrl);
Log.i(TAG, "onMessageReceived: action : "+action);
if (imageUrl == null) {
sendNotification(title,message,action);
} else {
new BigPictureNotification(this,title,message,imageUrl,action);
}
}
// [END receive_message]
1. 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
NOTE: Firebase team have not developed a UI to send data-messages to
your devices, yet. You should use your server for sending this type!
2. How to?
To achieve this, you have to perform a POST request to the following URL:
POST https://fcm.googleapis.com/fcm/send
Headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key=<your-server-key>
Body using topics
{
"to": "/topics/my_topic",
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Or if you want to send it to specific devices
{
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
},
"registration_ids": ["{device-token}","{device2-token}","{device3-token}"]
}
NOTE: Be sure you're not adding JSON key notification
NOTE: To get your server key, you can find it in the firebase console: Your project -> settings -> Project settings -> Cloud messaging -> Server Key
3. How to handle the push notification message?
This is how you handle the received message:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
// Manage data
}
To make firebase library to call your onMessageReceived() in the following cases
App in foreground
App in background
App has been killed
you must not put JSON key notification in your request to Firebase API but instead, use data, see below.
The following message will not call your onMessageReceived() when your app is in the background or killed, and you can't customize your notification.
{
"to": "/topics/journal",
"notification": {
"title" : "title",
"text": "data!",
"icon": "ic_notification"
}
}
but instead using this will work
{
"to": "/topics/dev_journal",
"data": {
"text":"text",
"title":"",
"line1":"Journal",
"line2":"刊物"
}
}
Basically, the message is sent in the argument RemoteMessage along with your data object as Map<String, String>, then you can manage the notification in onMessageReceived as in the snippet here
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
//you can get your text message here.
String text= data.get("text");
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
// optional, this is to make beautiful icon
.setLargeIcon(BitmapFactory.decodeResource(
getResources(), R.mipmap.ic_launcher))
.setSmallIcon(smallIcon) //mandatory
.......
/*You can read more on notification here:
https://developer.android.com/training/notify-user/build-notification.html
https://www.youtube.com/watch?v=-iog_fmm6mE
*/
}
I feel like all the responses are incomplete but all of them have something that you need to process a notification that has data when your app is in the background.
Follow these steps and you will be able to process your notifications when your app is in the background.
Add an intent-filter like this:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
to an activity that you want to process the notification data.
Send notifications with the next format:
{
"notification" : {
"click_action" : ".MainActivity",
"body" : "new Symulti update !",
"title" : "new Symulti update !",
"icon" : "ic_notif_symulti" },
"data": { ... },
"to" : "c9Vaa3ReGdk:APA91bH-AuXgg3lDN2WMcBrNhJZoFtYF9" }
The key here is add
"click_action" : ".MainActivity"
where .MainActivity is the activity with the intent-filter that you added in step 1.
Get data info from notification in the onCreate of .MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get notification data info
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
//bundle must contain all info sent in "data" field of the notification
}
}
And that should be all you need to do.
According to the firebase documentation in send downstream using firebase, there is 2 type of payload :
data
This parameter specifies the custom key-value pairs of the message's payload.
Client app is responsible for processing data messages. Data messages have only custom key-value pairs.
notification
This parameter specifies the predefined, user-visible key-value pairs of the notification payload. FCM automatically displays the message to end-user devices on behalf of the client app. Notification messages have a predefined set of user-visible keys.
When you are in the foreground you can get the data inside FCM using onMessageReceived(), you can get your data from data payload.
data = remoteMessage.getData();
String customData = (String) data.get("customData");
When you are in background, FCM will showing notification in system tray based on the info from notification payload. Title, message, and icon which used for the notification on system tray are get from the notification payload.
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
This notification payload are used when you want to automactically showing notification on the system tray when your app is in the background.
To get notification data when your app in the background, you should add click_action inside notification payload.
If you want to open your app and perform a specific action [while backgrounded], set click_action in the notification payload and map it to an intent filter in the Activity you want to launch. For example, set click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the following:
<intent-filter>
<action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Put that intent-filter on your manifest, inside one of your activity tag. When you click the notification, it will open the app and go straight to activity that you define in click_action, in this case "OPEN_ACTIVTY_1".
And inside that activity you can get the data by :
Bundle b = getIntent().getExtras();
String someData = b.getString("someData");
I'm using FCM for my android app and use both of the payload.
Here is the example JSON i'm using :
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
Working as of July 2019
Android compileSdkVersion 28, buildToolsVersion 28.0.3 and firebase-messaging:19.0.1
After many many hours of researching through all of the other StackOverflow questions and answers, and trying innumerable outdated solutions, this solution managed to show notifications in these 3 scenarios:
- App is in foreground:
the notification is received by the onMessageReceived method at my MyFirebaseMessagingService class
- App has been killed (it is not running in background):
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is launched by calling the activity that has android.intent.category.LAUNCHER in the manifest. You can get the data part of the notification by using getIntent().getExtras() at the onCreate() method.
- App is in background:
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is brought to the foreground by launching the activity that has android.intent.category.LAUNCHER in the manifest. As my app has launchMode="singleTop" in that activity, the onCreate() method is not called because one activity of the same class is already created, instead the onNewIntent() method of that class is called and you get the data part of the notification there by using intent.getExtras().
Steps:
1- If you define your app's main activity like this:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:largeHeap="true"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name=".MainActivity" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2- add these lines at the onCreate() method of your MainActivity.class
Intent i = getIntent();
Bundle extras = i.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onCreate: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
and these methods to the same MainActivity.class:
#Override
public void onNewIntent(Intent intent){
//called when a new intent for this class is created.
// The main case is when the app was in background, a notification arrives to the tray, and the user touches the notification
super.onNewIntent(intent);
Log.d(Application.APPTAG, "onNewIntent - starting");
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onNewIntent: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
}
private void showNotificationInADialog(String title, String message) {
// show a dialog with the provided title and message
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
3- create the class MyFirebase like this:
package com.yourcompany.app;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
public MyFirebaseMessagingService() {
super();
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(Application.APPTAG, "myFirebaseMessagingService - onMessageReceived - message: " + remoteMessage);
Intent dialogIntent = new Intent(this, NotificationActivity.class);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra("msg", remoteMessage);
startActivity(dialogIntent);
}
}
4- create a new class NotificationActivity.class like this:
package com.yourcompany.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import com.google.firebase.messaging.RemoteMessage;
public class NotificationActivity extends AppCompatActivity {
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
Bundle extras = getIntent().getExtras();
Log.d(Application.APPTAG, "NotificationActivity - onCreate - extras: " + extras);
if (extras == null) {
context.finish();
return;
}
RemoteMessage msg = (RemoteMessage) extras.get("msg");
if (msg == null) {
context.finish();
return;
}
RemoteMessage.Notification notification = msg.getNotification();
if (notification == null) {
context.finish();
return;
}
String dialogMessage;
try {
dialogMessage = notification.getBody();
} catch (Exception e){
context.finish();
return;
}
String dialogTitle = notification.getTitle();
if (dialogTitle == null || dialogTitle.length() == 0) {
dialogTitle = "";
}
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.myDialog));
builder.setTitle(dialogTitle);
builder.setMessage(dialogMessage);
builder.setPositiveButton(getResources().getString(R.string.accept), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
5- Add these lines to your app Manifest, inside your tags
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="#string/default_notification_channel_id"/>
<activity android:name=".NotificationActivity"
android:theme="#style/myDialog"> </activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/notification_icon"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/color_accent" />
6- add these lines in your Application.java onCreate() method, or in MainActivity.class onCreate() method:
// notifications channel creation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String channelId = getResources().getString("default_channel_id");
String channelName = getResources().getString("General announcements");
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_LOW));
}
Done.
Now for this to work well in the 3 mentioned scenarios, you have to send the notification from the Firebase web console in the following way:
In the Notification section:
Notification Title = Title to display in the notification dialog (optional)
Notification text = Message to show to the user (required)
Then in the Target section:
App = your Android app
and in Additional Options section:
Android Notification Channel = default_channel_id
Custom Data
key: title value: (same text here than in the Title field of the Notification section)
key: body value: (same text here than in the Message field of the Notification section)
key:click_action value: .MainActivity
Sound=Disabled
Expires=4 weeks
You can debug it in the Emulator with API 28 with Google Play.
Happy coding!
According to docs
Handle messages in a backgrounded app
When your app is in the background, Android directs notification
messages to the system tray. A user tap on the notification opens the
app launcher by default.
This includes messages that contain both notification and data
payload. In these cases, the notification is delivered to the device's
system tray, and the data payload is delivered in the extras of the
intent of your launcher Activity.
If you want to open your app and perform a specific action, set
click_action in the notification payload and map it to an intent
filter in the Activity you want to launch. For example, set
click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the
following:
<intent-filter> <action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Edit :
Based on this thread :
You can't set click_action payload using Firebase Console. You could try testing with a curl command or a custom http server
curl --header "Authorization: key=<YOUR_KEY_GOES_HERE>"
--header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send
-d "{\"to\":\"/topics/news\",\"notification\":
{\"title\": \"Click Action Message\",\"text\": \"Sample message\",
\"click_action\":\"OPEN_ACTIVITY_1\"}}"
To capture the message in background you need to use a BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.legacy.content.WakefulBroadcastReceiver
import com.google.firebase.messaging.RemoteMessage
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
val TAG: String = FirebaseBroadcastReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val dataBundle = intent.extras
if (dataBundle != null)
for (key in dataBundle.keySet()) {
Log.d(TAG, "dataBundle: " + key + " : " + dataBundle.get(key))
}
val remoteMessage = RemoteMessage(dataBundle)
}
}
and add this to your manifest:
<receiver
android:name="MY_PACKAGE_NAME.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
Since the display-messages which are sent from Firebase Notification UI only works if your app is in foreground. For data-messages, there is a need to make a POST call to FCM
Steps
Install Advanced Rest Client Google Chrome Extension
Add the following headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key="your server key"
Add the body
If using topics :
{
"to" : "/topics/topic_name",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
If using registration id :
{
"registration_ids" : "[{"id"},{id1}]",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
Thats it!. Now listen to onMessageReceived callback as usual.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String value1 = data.get("key1");
String value2 = data.get("key2");
}
According to the docs: May 17, 2017
When your app is in the background, Android
directs notification messages to the system tray. A user tap on the
notification opens the app launcher by default.
This includes messages that contain both notification and data payload
(and all messages sent from the Notifications console). In these
cases, the notification is delivered to the device's system tray, and
the data payload is delivered in the extras of the intent of your
launcher Activity.
So,you should use both of the payload notification + data:
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
There is no need to use click_action.You should just get exras from intent on LAUNCHER activity
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Java code should be on onCreate method on MainActivity :
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
Bundle extras = intent.getExtras();
String someData= extras.getString("someData");
String someData2 = extras.getString("someData2");
}
You can test both of the payload notification + data from Firebase Notifications Console . Don't forget to fill custom data fields on Advanced options section
Here is more clear concepts about firebase message. I found it from their support team.
Firebase has three message types:
Notification messages : Notification message works on background or foreground. When app is in background, Notification messages are delivered to the system tray. If the app is in the foreground, messages are handled by onMessageReceived() or didReceiveRemoteNotification callbacks. These are essentially what is referred to as Display messages.
Data messages: On Android platform, data message can work on background and foreground. The data message will be handled by onMessageReceived(). A platform specific note here would be: On Android, the data payload can be retrieved in the Intent used to launch your activity. To elaborate, if you have "click_action":"launch_Activity_1", you can retrieve this intent through getIntent() from only Activity_1.
Messages with both notification and data payloads: When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification. When in the foreground, your app receives a message object with both payloads available. Secondly, the click_action parameter is often used in notification payload and not in data payload. If used inside data payload, this parameter would be treated as custom key-value pair and therefore you would need to implement custom logic for it to work as intended.
Also, I recommend you to use onMessageReceived method (see Data message) to extract the data bundle. From your logic, I checked the bundle object and haven't found expected data content. Here is a reference to a similar case which might provide more clarity.
For more info visit my this thread
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
}
is not called every time it is called only when app is in forground
there is one override method this method is called every time , no matter what app is in foreground or in background or killed but this method is available with this firebase api version
this is the version u have to import from gradle
compile 'com.google.firebase:firebase-messaging:10.2.1'
this is the method
#Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
// you can get ur data here
//intent.getExtras().get("your_data_key")
}
with previous firebase api this method was not there so in that case
fire base handle itself when app is in background .... now u have this method
what ever u want to do ... u can do it here in this method .....
if you are using previous version than default activity will start
in that case u can get data same way
if(getIntent().getExtras() != null && getIntent().getExtras().get("your_data_key") != null) {
String strNotificaiton = getIntent().getExtras().get("your_data_key").toString();
// do what ever u want ....
}
generally this is the structure from server we get in notification
{
"notification": {
"body": "Cool offers. Get them before expiring!",
"title": "Flat 80% discount",
"icon": "appicon",
"click_action": "activity name" //optional if required.....
},
"data": {
"product_id": 11,
"product_details": "details.....",
"other_info": "......."
}
}
it's up to u how u want to give that data key or u want give notification anything u can give .......
what ever u will give here with same key u will get that data .........
there are few cases if u r not sending click action in that case when u will click on notification default activity will open , but if u want to open your specific activity when app is in background u can call your activity from this on handleIntent method because this is called every time
Simple summary like this
if your app is running;
onMessageReceived()
is triggers.
if your app is not running (killed by swiping) ;
onMessageReceived()
is not triggered and delivered by direclty. If you have any specialy key-value pair. They don' t work beacuse of onMessageReceived() not working.
I' ve found this way;
In your launcher activity, put this logic,
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.activity_splash);
if (getIntent().getExtras() != null && getIntent().getExtras().containsKey("PACKAGE_NAME")) {
// do what you want
// and this for killing app if we dont want to start
android.os.Process.killProcess(android.os.Process.myPid());
} else {
//continue to app
}
}
in this if block, search for your keys according to firebase UI.
In this example my key and value like above; (sorry for language =))
When my code works, i get "com.rda.note".
android.os.Process.killProcess(android.os.Process.myPid());
with this line of code, i closed my application and open Google Play Market
happy coding =)
2017 updated answer
Here is a clear-cut answer from the docs regarding this:
I figured out the scenarios,
When app is in foreground,
onMessageReceived() method is called from the FirebaseService.So the pendingIntent defined in the service class will be called.
And when app is in background, first activity is called.
Now, if you use a splash activity, then must keep in mind the splashactivity will be called, else if there is no splashActivity, then whatever the first activity is, will be called.
Then you need to check getIntent() of the firstActivity to see if it has any bundle .if everything is alright you will see bundle is there with values filled in. If the value in data tag sent from server looks like this,
"data": {
"user_name": "arefin sajib",
"value": "user name notification"
}
Then in the first activity, you will see,
there is a valid intent( getIntent() is not null) , valid bundle and inside bundle , there will the whole JSON mentioned above with data as key.
For this scenario, code for extraction of value will look like this,
if(getIntent()!=null){
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
try {
JSONObject object = new JSONObject(bundle.getStringExtra("data"));
String user_name = object.optString("user_name");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
I had same problem. After some digging why my MainActivity is called with intent without data I realized that my LAUNCHER activity (as in Manifest) is SplashActivity. There I found the message data and forwarded them to MainActivity. Works like sharm. I beleive this can help someone.
Thanks for all another answers.
Remove notification payload completely from your server request. Send only data and handle it in onMessageReceived(), otherwise your onMessageReceived will not be triggered when the app is in background or killed.
Here is what I am sending from server:
{
"data":{
"id": 1,
"missedRequests": 5
"addAnyDataHere": 123
},
"to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......"
}
So you can receive your data in onMessageReceived(RemoteMessage message) like this: (let's say I have to get the id)
Object obj = message.getData().get("id");
if (obj != null) {
int id = Integer.valueOf(obj.toString());
}
And similarly you can get any data which you have sent from server within onMessageReceived().
Thanks to All of you for your Answers. But I solved this by sending data message instead of sending Notification.
Server code
<?php
$url = "https://fcm.googleapis.com/fcm/send";
$token = "C-l6T_a7HouUK****";
$serverKey = "AAAAaOcKS00:********";
define( 'API_ACCESS_KEY', $serverKey );
$registrationIds = array($token);
// prep the bundle
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'subtitle' => 'This is a subtitle. subtitle',
'tickerText' => 'Ticker text here...Ticker text here...Ticker text
here',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send'
);
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
?>
And caught the Data in onMessageReceived
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("message"));
}
// Check if message contains a notification payload.
else if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
sendNotification(remoteMessage.getNotification().getBody());
}
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, Notify.class).putExtra("msg",messageBody);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = "idddd";
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(MyFirebaseMessagingService.this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("FCM Message")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
In general
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
Data Messages example:
{
"to": "/path",
"data":
{
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Display Messages example:
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
Android side can handle notifications like:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
…
#Override public void onMessageReceived(RemoteMessage remoteMessage){
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
}
…
}
More details about FCM you can find here : Set up a Firebase Cloud Messaging client app on Android
I added the following code in the firebase-messaging-sw.js,
messaging.onBackgroundmessage((payload)=>{
console.log("background message detected!!");
console.log("message : ", payload);
})
this was triggered every time message received in background. but I was unable to use the payload in the main thread as SW didn't supported it. so then I researched a lot and found a solution in an Android forum.
so the solution is that we will have to remove the notification payload from the request payload.
so I changed my payload from
{
"notification": {
"title": "Hey there",
"body": "Subscribe to AMAL MOHAN N youtube channel"
},
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
to
{
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
change in the payload automatically made the receiveMessage() trigger in foreground messages as well as background messages.
I found this in an Android forum and this worked for me! please let me know if this works for you.
The easy way to send messages even if the app is in background and foreground as follow:-
To send a message using API, you can use a tool called AdvancedREST Client, its a chrome extension, and send a message with the following parameters.
Rest client tool Link: https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo
use this url:- https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=Your Server key From or Authoization key(see below ref)
{ "data": {
"image": "https://static.pexels.com/photos/4825/red-love-romantic-flowers.jpg",
"message": "Firebase Push Message Using API"
"AnotherActivity": "True"
},
"to" : "device id Or Device token"
}
Authorization key can be obtained by visiting Google developers console and click on Credentials button on the left menu for your project. Among the API keys listed, the server key will be your authorization key.
And you need to put tokenID of the receiver in the “to” section of your POST request sent using API.
you want to work onMessageReceived(RemoteMessage remoteMessage) in background send only data part notification part this:
"data": "image": "", "message": "Firebase Push Message Using API",
"AnotherActivity": "True", "to" : "device id Or Device token"
By this onMessageRecivied is call background and foreground no need to handle notification using notification tray on your launcher activity.
Handle data payload in using this:
public void onMessageReceived(RemoteMessage remoteMessage)
if (remoteMessage.getData().size() > 0)
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
The solutions provided didn't work in my case.
At the end of the day, I figured out that some battery optimization apps give users the control to allow or block apps from sending notifications. In my case it was Samsung's Smart Manager that was automatically blocking my app as soon as it was killed/cleaned from the recents list.
Turning off that feature for my app was the only solution I could find.
To be able to retrieve data from firebase notification sent when app is in background, you need to add click_action entry in your notification data set.
Set additional options of your notification in firebase console like this: (you have to include any extra data that you want to retrieve in your app here):
And include the intent filter in your manifest file under the activity to be launched
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.MyApp.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<intent-filter>
<action android:name="FIREBASE_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Then get the bundle data in your activity onNewIntent:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle data = intent.getExtras();
if (data != null) {
for (String key : data.keySet()) {
Object value = data.get(key);
// do what you want with the data entries
Log.d(FIREBASE_TAG, "Key: " + key + " Value: " + value);
Toast.makeText(this, "Key: "+key+".... Value: "+value, Toast.LENGTH_LONG).show;
}
}
}
When your app is in foreground, you can set your onMessageReceived like this:
#Override
public void onMessageReceived(#NonNull RemoteMessage message) {
Log.d(FIREBASE_TAG, "Message From: " + message.getFrom());
if (message.getNotification() != null) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Map<String, String> data = message.getData();
if(data != null && !data.isEmpty()){
for(Map.Entry<String ,String > entry : data.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
}
//.......
// implement the rest of the code to show notification
//
}
}
June 2018 Answer -
You have to make sure there is not a "notification" keyword anywhere in the message. Only include "data", and the app will be able to handle the message in onMessageReceived, even if in background or killed.
Using Cloud Functions:
const message = {
token: token_id, // obtain device token id by querying data in firebase
data: {
title: "my_custom_title",
body: "my_custom_body_message"
}
}
return admin.messaging().send(message).then(response => {
// handle response
});
Then in your onMessageReceived(), in your class extending com.google.firebase.messaging.FirebaseMessagingService :
if (data != null) {
Log.d(TAG, "data title is: " + data.get("title");
Log.d(TAG, "data body is: " + data.get("body");
}
// build notification using the body, title, and whatever else you want.
According to OAUTH 2.0:
There will be Auth problem for this case beacuse FCM now using OAUTH 2
So I read firebase documentation and according to documentation new way to post data message is;
POST: https://fcm.googleapis.com/v1/projects/YOUR_FIREBASEDB_ID/messages:send
Headers
Key: Content-Type, Value: application/json
Auth
Bearer YOUR_TOKEN
Example Body
{
"message":{
"topic" : "xxx",
"data" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
In the url there is Database Id which you can find it on your firebase console. (Go project setttings)
And now lets take our token (It will valid only 1 hr):
First in the Firebase console, open Settings > Service Accounts. Click Generate New Private Key, securely store the JSON file containing the key. I was need this JSON file to authorize server requests manually. I downloaded it.
Then I create a node.js project and used this function to get my token;
var PROJECT_ID = 'YOUR_PROJECT_ID';
var HOST = 'fcm.googleapis.com';
var PATH = '/v1/projects/' + PROJECT_ID + '/messages:send';
var MESSAGING_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging';
var SCOPES = [MESSAGING_SCOPE];
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
getAccessToken().then(function(accessToken) {
console.log("TOKEN: "+accessToken)
})
});
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./YOUR_DOWNLOADED_JSON_FILE.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
Now I can use this token in my post request. Then I post my data message, and it is now handled by my apps onMessageReceived function.
Since 2019, Google Firebase has a big change in their APIs
I mean:
'com.google.firebase:firebase-messaging:18.0.0'
in 18.0.0 they removed MyFirebaseInstanceIDService and you need to get token in MyFirebaseMessagingService so you just need to write :
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
}
and also in your AndroidManifest.xml, you have to remove :
<service android:name=".service.MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Also, you're recommended to set default values to customize the appearance of notifications. You can specify a custom default icon and a custom default color that are applied whenever equivalent values are not set in the notification payload.
Add these lines inside the application tag to set the custom default icon and custom color:
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorAccent" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/push_channel" />
now to handle notification messages in a backgrounded app you should define an Intent in your first Activity even if it is SplashScreen, When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.
for example, if your Json is like this:
"data": {
"message": "2",
"title": "1",
"pushType" : "banner",
"bannerLink": "http://www.google.com",
"image" : "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"}
you just need to write a simple intent to get those values:
Bundle extras = intent.getExtras();
String bannerLink = extras.getString("bannerLink");
...
String channelId = extras.getString("channelId");
In addition to above answers,
If you are testing push notifications using FCM console, 'data' key and object is not added to Push Notification bundle. So you will not receive detailed push notification when App is background or killed.
In this case you have to opt for your back end admin console to test App background scenario.
Here, you will have added 'data' key to your push bundle. so, detailed push will be shown as expected.
Hope this helps few.
Using this code you can get the notification in background/foreground and also put action:
//Data should come in this format from the notification
{
"to": "/xyz/Notifications",
"data": {
"key1": "title notification",
"key2": "description notification"
}
}
In-App use this code:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String key1Data = remoteMessage.getData().get("key1");
// use key1Data to according to your need
}
I solved this issue by using Broadcast Message.
create an Broadcast and send the payload in broadcast Message from the service worker.
then Receive the payload inside application and handle it the way you want.
This case is only for firebase admin notification
If the application is in the background FirebaseMessagingService does not get triggered
For handling this case go to launcher activity and check for the intent bundle which is attached, and print out all data using this code :
intent?.extras?.let {it->
for (key in bundle.keySet()) {
val value = bundle[key]
Log.d("NotificationData", String.format("%s %s (%s)", key, value.toString(), value!!.javaClass.name))
}
}
check-in log all data with data types is displayed ;
Example :
I want to get the Week and title from notifications will use this code
intent?.extras?.let {it->
if (it.containsKey("week")){
}
if (it.containsKey("title")){
}
I'm working with Firebase and testing sending notifications to my app from my server while the app is in the background. The notification is sent successfully, it even appears on the notification centre of the device, but when the notification appears or even if I click on it, the onMessageReceived method inside my FCMessagingService is never called.
When I tested this while my app was in the foreground, the onMessageReceived method was called and everything worked fine. The problem occurs when the app is running in the background.
Is this intended behaviour, or is there a way I can fix this?
Here is my FBMessagingService:
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class FBMessagingService extends FirebaseMessagingService {
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.i("PVL", "MESSAGE RECEIVED!!");
if (remoteMessage.getNotification().getBody() != null) {
Log.i("PVL", "RECEIVED MESSAGE: " + remoteMessage.getNotification().getBody());
} else {
Log.i("PVL", "RECEIVED MESSAGE: " + remoteMessage.getData().get("message"));
}
}
}
This is working as intended, notification messages are delivered to your onMessageReceived callback only when your app is in the foreground. If your app is in the background or closed then a notification message is shown in the notification center, and any data from that message is passed to the intent that is launched as a result of the user tapping on the notification.
You can specify a click_action in your JSON to indicate the intent that should be launched when the notification is tapped by the user. The main activity is used if no click_action is specified.
When the intent is launched you can use the
getIntent().getExtras();
to retrieve a Set that would include any data sent along with the notification message.
For more on notification message see docs.
Remove notification field completely from your server request. Send only data and handle it in onMessageReceived() otherwise your onMessageReceived() will not be triggered when app is in background or killed.
Don't forget to include "priority": "high" field in your notification request. According to the documentation: data messages are sent with a normal priority, thus they will not arrive instantly; it could also be the problem.
Here is what I am sending from server
{
"data":{
"id": 1,
"missedRequests": 5
"addAnyDataHere": 123
},
"to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......",
"priority": "high"
}
So you can receive your data in onMessageReceived(RemoteMessage message) like this....let say I have to get id
Object obj = message.getData().get("id");
if (obj != null) {
int id = Integer.valueOf(obj.toString());
}
this method handleIntent() has been depreciated, so handling a notification can be done as below:
Foreground State: The click of the notification will go to the pending Intent's activity which you are providing while creating a notification pro-grammatically as it generally created with data payload of the notification.
Background/Killed State - Here, the system itself creates a notification based on notification payload and clicking on that notification will take you to the launcher activity of the application where you can easily fetch Intent data in any of your life-cycle methods.
Here is more clear concepts about firebase message. I found it from their support team.
Firebase has three message types:
Notification messages : Notification message works on background or foreground. When app is in background, Notification messages are delivered to the system tray. If the app is in the foreground, messages are handled by onMessageReceived() or didReceiveRemoteNotification callbacks. These are essentially what is referred to as Display messages.
Data messages: On Android platform, data message can work on background and foreground. The data message will be handled by onMessageReceived(). A platform specific note here would be: On Android, the data payload can be retrieved in the Intent used to launch your activity. To elaborate, if you have "click_action":"launch_Activity_1", you can retrieve this intent through getIntent() from only Activity_1.
Messages with both notification and data payloads: When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification. When in the foreground, your app receives a message object with both payloads available. Secondly, the click_action parameter is often used in notification payload and not in data payload. If used inside data payload, this parameter would be treated as custom key-value pair and therefore you would need to implement custom logic for it to work as intended.
Also, I recommend you to use onMessageReceived method (see Data message) to extract the data bundle. From your logic, I checked the bundle object and haven't found expected data content. Here is a reference to a similar case which might provide more clarity.
From server side, firebase notification should bellow format:
Server side should send "notification" object. Lacks of "notification" object in my TargetActivity didn't getting message using getIntent().
Correct message format is given bellow:
{
"data": {
"body": "here is body",
"title": "Title"
},
"notification": {
"body": "here is body",
"title": "Title",
"click_action": "YOUR_ACTION"
},
"to": "ffEseX6vwcM:APA91bF8m7wOF MY FCM ID 07j1aPUb"
}
Here is more clear concepts about firebase message. I found it from their support team.
For more info visit my this thread and this thread
I had the same problem. It is easier to use the 'data message' instead of the 'notification'. The data message always load the class onMessageReceived.
In that class you can make your own notification with the notificationbuilder.
Example:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
sendNotification(remoteMessage.getData().get("title"),remoteMessage.getData().get("body"));
}
private void sendNotification(String messageTitle,String messageBody) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0 /* request code */, intent,PendingIntent.FLAG_UPDATE_CURRENT);
long[] pattern = {500,500,500,500,500};
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(messageTitle)
.setContentText(messageBody)
.setAutoCancel(true)
.setVibrate(pattern)
.setLights(Color.BLUE,1,1)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
As per Firebase Cloud Messaging documentation-If Activity is in foreground then onMessageReceived will get called. If Activity is in background or closed then notification message is shown in the notification center for app launcher activity.
You can call your customized activity on click of notification if your app is in background by calling rest service api for firebase messaging as:
URL-https://fcm.googleapis.com/fcm/send
Method Type- POST
Header- Content-Type:application/json
Authorization:key=your api key
Body/Payload:
{ "notification": {
"title": "Your Title",
"text": "Your Text",
"click_action": "OPEN_ACTIVITY_1" // should match to your intent filter
},
"data": {
"keyname": "any value " //you can get this data as extras in your activity and this data is optional
},
"to" : "to_id(firebase refreshedToken)"
}
And with this in your app you can add below code in your activity to be called:
<intent-filter>
<action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
onMessageReceived(RemoteMessage remoteMessage) method called based on the following cases.
FCM Response With notification and data block:
{
"to": "device token list",
"notification": {
"body": "Body of Your Notification",
"title": "Title of Your Notification"
},
"data": {
"body": "Body of Your Notification in Data",
"title": "Title of Your Notification in Title",
"key_1": "Value for key_1",
"image_url": "www.abc.com/xyz.jpeg",
"key_2": "Value for key_2"
}
}
App in Foreground:
onMessageReceived(RemoteMessage remoteMessage) called, shows LargeIcon and BigPicture in the notification bar. We can read the content from both notification and data block
App in Background:
onMessageReceived(RemoteMessage remoteMessage) not called, system tray will receive the message and read body and title from notification block and shows default message and title in the notification bar.
FCM Response With only data block:
In this case, removing notification blocks from json
{
"to": "device token list",
"data": {
"body": "Body of Your Notification in Data",
"title": "Title of Your Notification in Title",
"key_1": "Value for key_1",
"image_url": "www.abc.com/xyz.jpeg",
"key_2": "Value for key_2"
}
}
Solution for calling onMessageReceived()
App in Foreground:
onMessageReceived(RemoteMessage remoteMessage) called, shows LargeIcon and BigPicture in the notification bar. We can read the content from both notification and data block
App in Background:
onMessageReceived(RemoteMessage remoteMessage) called, system tray will not receive the message because of notification key is not in the response. Shows LargeIcon and BigPicture in the notification bar
Code
private void sendNotification(Bitmap bitmap, String title, String
message, PendingIntent resultPendingIntent) {
NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle();
style.bigPicture(bitmap);
Uri defaultSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
String NOTIFICATION_CHANNEL_ID = mContext.getString(R.string.default_notification_channel_id);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "channel_name", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(notificationChannel);
}
Bitmap iconLarge = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.mdmlogo);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.mdmlogo)
.setContentTitle(title)
.setAutoCancel(true)
.setSound(defaultSound)
.setContentText(message)
.setContentIntent(resultPendingIntent)
.setStyle(style)
.setLargeIcon(iconLarge)
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_MAX)
.setChannelId(NOTIFICATION_CHANNEL_ID);
notificationManager.notify(1, notificationBuilder.build());
}
Reference Link:
https://firebase.google.com/docs/cloud-messaging/android/receive
If app is in the background mode or inactive(killed), and you click on Notification, you should check for the payload in LaunchScreen(in my case launch screen is MainActivity.java).
So in MainActivity.java on onCreate check for Extras:
if (getIntent().getExtras() != null) {
for (String key : getIntent().getExtras().keySet()) {
Object value = getIntent().getExtras().get(key);
Log.d("MainActivity: ", "Key: " + key + " Value: " + value);
}
}
I got the same issue. If the app is foreground - it triggers my background service where I can update my database based on the notification type.
But, the app goes to the background - the default notification service will be taken care to show the notification to the user.
Here is my solution to identify app in background and trigger your background service,
public class FirebaseBackgroundService extends WakefulBroadcastReceiver {
private static final String TAG = "FirebaseService";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "I'm in!!!");
if (intent.getExtras() != null) {
for (String key : intent.getExtras().keySet()) {
Object value = intent.getExtras().get(key);
Log.e("FirebaseDataReceiver", "Key: " + key + " Value: " + value);
if(key.equalsIgnoreCase("gcm.notification.body") && value != null) {
Bundle bundle = new Bundle();
Intent backgroundIntent = new Intent(context, BackgroundSyncJobService.class);
bundle.putString("push_message", value + "");
backgroundIntent.putExtras(bundle);
context.startService(backgroundIntent);
}
}
}
}
}
In the manifest.xml
<receiver android:exported="true" android:name=".FirebaseBackgroundService" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
Tested this solution in latest android 8.0 version. Thanks
Override the handleIntent Method of the FirebaseMessageService works for me.
here the code in C# (Xamarin)
public override void HandleIntent(Intent intent)
{
try
{
if (intent.Extras != null)
{
var builder = new RemoteMessage.Builder("MyFirebaseMessagingService");
foreach (string key in intent.Extras.KeySet())
{
builder.AddData(key, intent.Extras.Get(key).ToString());
}
this.OnMessageReceived(builder.Build());
}
else
{
base.HandleIntent(intent);
}
}
catch (Exception)
{
base.HandleIntent(intent);
}
}
and thats the Code in Java
public void handleIntent(Intent intent)
{
try
{
if (intent.getExtras() != null)
{
RemoteMessage.Builder builder = new RemoteMessage.Builder("MyFirebaseMessagingService");
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);
}
}
By default the Launcher Activity in you app will be launched when your app is in background and you click the notification, if you have any data part with your notifcation you can handle it in the same activity as follows,
if(getIntent().getExtras()! = null){
//do your stuff
}else{
//do that you normally do
}
I might be very late here to answers but the official documentation is a bit confusing.
Also it's clearly stated that there are two types of notification
Notification message: Automatically handled by FCM
Data message: Handled by the client app.
No doubt if the server sends a Data message then onMessageReceived() methods definitely get invoked but in the case of Notification message onMessageReceived() method will get invoked only if the app is in the foreground and when the app is in the background the data we are sending is just null.
Example:
Lets assume server is sending notification message type:
A. In case of Foreground:
remoteMessage.data["key"] will work
B. In case of background:
-remoteMessage.data["key"] will return null
but here if you find the same intent data in defaul activity with getIntent().getExtras().getString("key") will work
C. In case of kill:
-remoteMessage.data["key"] will return null
but here if you find the same intent data in defaul activity with getIntent().getExtras().getString("key") will work
Now, let's assume server is sending data message type:
D. In case of Foreground:
remoteMessage.data["key"] will work
E. In case of background:
remoteMessage.data["key"] will work
F. In case of kill:
remoteMessage.data["key"] will work
No doubt data message will always invokes onMessageReceived() method but in case of notification message and app is in background/kill state you can use the solution of B. Thanks
I hope it will save everyone's time.
according to the solution from t3h Exi i would like to post the clean code here. Just put it into MyFirebaseMessagingService and everything works fine if the app is in background mode. You need at least to compile com.google.firebase:firebase-messaging:10.2.1
#Override
public void handleIntent(Intent intent)
{
try
{
if (intent.getExtras() != null)
{
RemoteMessage.Builder builder = new RemoteMessage.Builder("MyFirebaseMessagingService");
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);
}
}
If app is in background Fire-base by default handling notification But if we want to our custom notification than we have to change our server side, which is responsible for to send our custom data(data payload)
Remove notification payload completely from your server request. Send only Data and handle it in onMessageReceived() otherwise your onMessageReceived will not be triggered when app is in background or killed.
now,your server side code format look like,
{
"collapse_key": "CHAT_MESSAGE_CONTACT",
"data": {
"loc_key": "CHAT_MESSAGE_CONTACT",
"loc_args": ["John Doe", "Contact Exchange"],
"text": "John Doe shared a contact in the group Contact Exchange",
"custom": {
"chat_id": 241233,
"msg_id": 123
},
"badge": 1,
"sound": "sound1.mp3",
"mute": true
}
}
NOTE: see this line in above code
"text": "John Doe shared a contact in the group Contact Exchange"
in Data payload you should use "text" parameter instead of "body" or "message" parameters for message description or whatever you want to use text.
onMessageReceived()
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.e(TAG, "From: " + remoteMessage.getData().toString());
if (remoteMessage == null)
return;
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
/* Log.e(TAG, "Data Payload: " + remoteMessage.getData().toString());*/
Log.e(TAG, "Data Payload: " + remoteMessage);
try {
Map<String, String> params = remoteMessage.getData();
JSONObject json = new JSONObject(params);
Log.e("JSON_OBJECT", json.toString());
Log.e(TAG, "onMessageReceived: " + json.toString());
handleDataMessage(json);
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.getMessage());
}
}
}
I had similar issue. Based on the answers and references mentioned in this page, here are my two cents on how I resolved my problem with the below approach:
The message format I had earlier was as below:
{
"notification": {
"title": "AppName",
"sound": null,
"body": "Hey!YouhaveaMessage"
},
"data": {
"param1": null,
"param2": [
238
],
"id": 1
},
"to": "--the device push token here--"
}
I modified the message format to the below:
{
"data": {
"title": "AppName",
"body": "Hey! You have a message",
"param1": null,
"param2": [
238
],
"id": 1
},
"priority": "high",
"to": " — device push token here — "
}
Then I retrieved the title, body and all the parameters from the "data" payload itself. This solved the problem and I could then get the OnMessageReceived callback even though the app is in the background.
I wrote a blog post explaining the same issue, you can find it here.
{
"notification": {
"title": "Notification Title",
"body": "Notification Body",
"click_action": "ActivityToOpen"
},
"data": {
"key": "value "
},
"to": "id"
}
If the FCM payload has notification{} block like the above and the app is in the background, the system builds the notification for you with the title and body given in notification{}. When the user clicks on it, the activity mentioned in click_action opens, if nothing is given the default launcher activity opens and data inside the data{} block can be accessed from
intent.extras // of the launcher activity
If the app is in the foreground the function onMessageReceived() in FirebaseMessagingService() class gets triggered. We will have to build the notification by ourselves and we can access the data as following:
val value = message.data.getOrDefault("key", "")
If the FCM payload is without notification block{} like following;
{
"data": {
"title": "Notification Title",
"body": "Notification Body",
"key": "value "
},
"to" : "id"
}
The function onMessageReceived() in FirebaseMessagingService() class gets triggered regardless of app being in background or foreground and we will have to build notification by ourselves.
We can access the data as following:
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
val title = message.data.getOrDefault("title", "")
val body = message.data.getOrDefault("body", "")
}
Just call this in your MainActivity's onCreate Method :
if (getIntent().getExtras() != null) {
// Call your NotificationActivity here..
Intent intent = new Intent(MainActivity.this, NotificationActivity.class);
startActivity(intent);
}
Try this:
public void handleIntent(Intent intent) {
try {
if (intent.getExtras() != null) {
RemoteMessage.Builder builder = new RemoteMessage.Builder("MyFirebaseMessagingService");
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);
}
}
I had this issue(app doesn't want to open on notification click if app is in background or closed), and the problem was an invalid click_action in notification body, try removing or changing it to something valid.
The point which deserves highlighting is that you have to use data message - data key only - to get onMessageReceived handler called even when the app is in background. You shouldn't have any other notification message key in your payload, otherwise the handler won't get triggered if the app is in background.
It is mentioned (but not so emphasized in FCM documentation) here:
https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages
Use your app server and FCM server API: Set the data key only. Can be
either collapsible or non-collapsible.
The backend I'm working with is using Notification messages and not Data messages. So after reading all the answers I tried to retrieve the extras from the bundle of the intent that comes to the launched activity.
But no matter which keys I tried to retrieve from getIntent().getExtras();, the value was always null.
However, I finally found a way to send data using Notification messages and retrieve it from the intent.
The key here is to add the data payload to the Notification message.
Example:
{
"data": {
"message": "message_body",
"title": "message_title"
},
"notification": {
"body": "test body",
"title": "test title"
},
"to": "E4An.."
}
After you do this, you will be able to get your info in this way:
intent.getExtras().getString("title")
will be message_title
and
intent.getExtras().getString("message")
will be message_body
Reference
If your problem is related to showing Big Image i.e. if you are sending push notification with an image from firebase console and it displays the image only if the app in the foreground. The solution for this problem is to send a push message with only data field. Something like this:
{ "data": { "image": "https://static.pexels.com/photos/4825/red-love-romantic-flowers.jpg", "message": "Firebase Push Message Using API" "AnotherActivity": "True" }, "to" : "device id Or Device token" }
When message is received and your app is in background the notification is sent to the extras intent of the main activity.
You can check the extra value in the oncreate() or onresume() function of the main activity.
You can check for the fields like data, table etc ( the one specified in the notification)
for example I sent using data as the key
public void onResume(){
super.onResume();
if (getIntent().getStringExtra("data")!=null){
fromnotification=true;
Intent i = new Intent(MainActivity.this, Activity2.class);
i.putExtra("notification","notification");
startActivity(i);
}
}
I think the answers to tell you change message type to data are clear to you.
But sometimes, if you cannot decide your the message type you received and you have to handle it. I post my method here. You just implemented FirebaseMessagingService and handle your message in handlIntent() method. From there you could customize your own notification. You can implement your own method sendYourNotificatoin()
class FCMPushService : FirebaseMessagingService() {
companion object {
private val TAG = "FCMPush"
}
override fun handleIntent(intent: Intent?) {
Logger.t(TAG).i("handleIntent:${intent.toString()}")
val data = intent?.extras as Bundle
val remoteMessage = RemoteMessage(data)
if (remoteMessage.data.isNotEmpty()) {
val groupId: String = remoteMessage.data[MESSAGE_KEY_GROUP_ID] ?: ""
val title = remoteMessage.notification?.title ?: ""
val body = remoteMessage.notification?.body ?: ""
if (title.isNotEmpty() && body.isNotEmpty())
sendYourNotificatoin(this, title, body, groupId)
}
}
}
I was having the same issue and did some more digging on this. When the app is in the background, a notification message is sent to the system tray, BUT a data message is sent to onMessageReceived()
See https://firebase.google.com/docs/cloud-messaging/downstream#monitor-token-generation_3
and https://github.com/firebase/quickstart-android/blob/master/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/MyFirebaseMessagingService.java
To ensure that the message you are sending, the docs say, "Use your app server and FCM server API: Set the data key only. Can be either collapsible or non-collapsible."
See https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages
There are two types of messages: notification messages and data messages.
If you only send data message, that is without notification object in your message string. It would be invoked when your app in background.
Check the answer of #Mahesh Kavathiya. For my case, in server code has only like this:
{
"notification": {
"body": "here is body",
"title": "Title",
},
"to": "sdfjsdfonsdofoiewj9230idsjkfmnkdsfm"
}
You need to change to:
{
"data": {
"body": "here is body",
"title": "Title",
"click_action": "YOUR_ACTION"
},
"notification": {
"body": "here is body",
"title": "Title"
},
"to": "sdfjsdfonsdofoiewj9230idsjkfmnkdsfm"
}
Then, in case app in Background, the default activity intent extra will get "data"
Good luck!
That is the intended behavior, you need to set click_action in firebase notification data set to be able to receive data from background.
See updated answer here:
https://stackoverflow.com/a/73724040/7904082
There are 2 types of Firebase push-notifications:
1- Notification message(Display message) ->
-- 1.1 If you choose this variant, the OS will create it self a notification if app is in Background and will pass the data in the intent. Then it's up to the client to handle this data.
-- 1.2 If the app is in Foreground then the notification it will be received via callback-function in the FirebaseMessagingService and it's up to the client to handle it.
2- Data messages(up to 4k data) -> These messages are used to send only data to the client(silently) and it's up to the client to handle it for both cases background/foreground via callback-function in FirebaseMessagingService
This is according official docs:https://firebase.google.com/docs/cloud-messaging/concept-options
Just override the OnCreate method of FirebaseMessagingService. It is called when your app is in background:
public override void OnCreate()
{
// your code
base.OnCreate();
}
Here is my manifest:
<service android:name=".fcm.PshycoFirebaseMessagingServices">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".fcm.PshycoFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
When the app is in the background and a notification arrives, then the default notification comes and doesn't run my code of onMessageReceived.
Here is my onMessageReceived code. This is invoked if my app is running on the foreground, not when it is running in the background. How can I run this code when the app is in background too?
// [START receive_message]
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
// If the application is in the foreground handle both data and notification messages here.
// 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.
data = remoteMessage.getData();
String title = remoteMessage.getNotification().getTitle();
String message = remoteMessage.getNotification().getBody();
String imageUrl = (String) data.get("image");
String action = (String) data.get("action");
Log.i(TAG, "onMessageReceived: title : "+title);
Log.i(TAG, "onMessageReceived: message : "+message);
Log.i(TAG, "onMessageReceived: imageUrl : "+imageUrl);
Log.i(TAG, "onMessageReceived: action : "+action);
if (imageUrl == null) {
sendNotification(title,message,action);
} else {
new BigPictureNotification(this,title,message,imageUrl,action);
}
}
// [END receive_message]
1. 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
NOTE: Firebase team have not developed a UI to send data-messages to
your devices, yet. You should use your server for sending this type!
2. How to?
To achieve this, you have to perform a POST request to the following URL:
POST https://fcm.googleapis.com/fcm/send
Headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key=<your-server-key>
Body using topics
{
"to": "/topics/my_topic",
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Or if you want to send it to specific devices
{
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
},
"registration_ids": ["{device-token}","{device2-token}","{device3-token}"]
}
NOTE: Be sure you're not adding JSON key notification
NOTE: To get your server key, you can find it in the firebase console: Your project -> settings -> Project settings -> Cloud messaging -> Server Key
3. How to handle the push notification message?
This is how you handle the received message:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
// Manage data
}
To make firebase library to call your onMessageReceived() in the following cases
App in foreground
App in background
App has been killed
you must not put JSON key notification in your request to Firebase API but instead, use data, see below.
The following message will not call your onMessageReceived() when your app is in the background or killed, and you can't customize your notification.
{
"to": "/topics/journal",
"notification": {
"title" : "title",
"text": "data!",
"icon": "ic_notification"
}
}
but instead using this will work
{
"to": "/topics/dev_journal",
"data": {
"text":"text",
"title":"",
"line1":"Journal",
"line2":"刊物"
}
}
Basically, the message is sent in the argument RemoteMessage along with your data object as Map<String, String>, then you can manage the notification in onMessageReceived as in the snippet here
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
//you can get your text message here.
String text= data.get("text");
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
// optional, this is to make beautiful icon
.setLargeIcon(BitmapFactory.decodeResource(
getResources(), R.mipmap.ic_launcher))
.setSmallIcon(smallIcon) //mandatory
.......
/*You can read more on notification here:
https://developer.android.com/training/notify-user/build-notification.html
https://www.youtube.com/watch?v=-iog_fmm6mE
*/
}
I feel like all the responses are incomplete but all of them have something that you need to process a notification that has data when your app is in the background.
Follow these steps and you will be able to process your notifications when your app is in the background.
Add an intent-filter like this:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
to an activity that you want to process the notification data.
Send notifications with the next format:
{
"notification" : {
"click_action" : ".MainActivity",
"body" : "new Symulti update !",
"title" : "new Symulti update !",
"icon" : "ic_notif_symulti" },
"data": { ... },
"to" : "c9Vaa3ReGdk:APA91bH-AuXgg3lDN2WMcBrNhJZoFtYF9" }
The key here is add
"click_action" : ".MainActivity"
where .MainActivity is the activity with the intent-filter that you added in step 1.
Get data info from notification in the onCreate of .MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get notification data info
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
//bundle must contain all info sent in "data" field of the notification
}
}
And that should be all you need to do.
According to the firebase documentation in send downstream using firebase, there is 2 type of payload :
data
This parameter specifies the custom key-value pairs of the message's payload.
Client app is responsible for processing data messages. Data messages have only custom key-value pairs.
notification
This parameter specifies the predefined, user-visible key-value pairs of the notification payload. FCM automatically displays the message to end-user devices on behalf of the client app. Notification messages have a predefined set of user-visible keys.
When you are in the foreground you can get the data inside FCM using onMessageReceived(), you can get your data from data payload.
data = remoteMessage.getData();
String customData = (String) data.get("customData");
When you are in background, FCM will showing notification in system tray based on the info from notification payload. Title, message, and icon which used for the notification on system tray are get from the notification payload.
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
This notification payload are used when you want to automactically showing notification on the system tray when your app is in the background.
To get notification data when your app in the background, you should add click_action inside notification payload.
If you want to open your app and perform a specific action [while backgrounded], set click_action in the notification payload and map it to an intent filter in the Activity you want to launch. For example, set click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the following:
<intent-filter>
<action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Put that intent-filter on your manifest, inside one of your activity tag. When you click the notification, it will open the app and go straight to activity that you define in click_action, in this case "OPEN_ACTIVTY_1".
And inside that activity you can get the data by :
Bundle b = getIntent().getExtras();
String someData = b.getString("someData");
I'm using FCM for my android app and use both of the payload.
Here is the example JSON i'm using :
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
Working as of July 2019
Android compileSdkVersion 28, buildToolsVersion 28.0.3 and firebase-messaging:19.0.1
After many many hours of researching through all of the other StackOverflow questions and answers, and trying innumerable outdated solutions, this solution managed to show notifications in these 3 scenarios:
- App is in foreground:
the notification is received by the onMessageReceived method at my MyFirebaseMessagingService class
- App has been killed (it is not running in background):
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is launched by calling the activity that has android.intent.category.LAUNCHER in the manifest. You can get the data part of the notification by using getIntent().getExtras() at the onCreate() method.
- App is in background:
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is brought to the foreground by launching the activity that has android.intent.category.LAUNCHER in the manifest. As my app has launchMode="singleTop" in that activity, the onCreate() method is not called because one activity of the same class is already created, instead the onNewIntent() method of that class is called and you get the data part of the notification there by using intent.getExtras().
Steps:
1- If you define your app's main activity like this:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:largeHeap="true"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name=".MainActivity" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2- add these lines at the onCreate() method of your MainActivity.class
Intent i = getIntent();
Bundle extras = i.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onCreate: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
and these methods to the same MainActivity.class:
#Override
public void onNewIntent(Intent intent){
//called when a new intent for this class is created.
// The main case is when the app was in background, a notification arrives to the tray, and the user touches the notification
super.onNewIntent(intent);
Log.d(Application.APPTAG, "onNewIntent - starting");
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onNewIntent: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
}
private void showNotificationInADialog(String title, String message) {
// show a dialog with the provided title and message
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
3- create the class MyFirebase like this:
package com.yourcompany.app;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
public MyFirebaseMessagingService() {
super();
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(Application.APPTAG, "myFirebaseMessagingService - onMessageReceived - message: " + remoteMessage);
Intent dialogIntent = new Intent(this, NotificationActivity.class);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra("msg", remoteMessage);
startActivity(dialogIntent);
}
}
4- create a new class NotificationActivity.class like this:
package com.yourcompany.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import com.google.firebase.messaging.RemoteMessage;
public class NotificationActivity extends AppCompatActivity {
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
Bundle extras = getIntent().getExtras();
Log.d(Application.APPTAG, "NotificationActivity - onCreate - extras: " + extras);
if (extras == null) {
context.finish();
return;
}
RemoteMessage msg = (RemoteMessage) extras.get("msg");
if (msg == null) {
context.finish();
return;
}
RemoteMessage.Notification notification = msg.getNotification();
if (notification == null) {
context.finish();
return;
}
String dialogMessage;
try {
dialogMessage = notification.getBody();
} catch (Exception e){
context.finish();
return;
}
String dialogTitle = notification.getTitle();
if (dialogTitle == null || dialogTitle.length() == 0) {
dialogTitle = "";
}
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.myDialog));
builder.setTitle(dialogTitle);
builder.setMessage(dialogMessage);
builder.setPositiveButton(getResources().getString(R.string.accept), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
5- Add these lines to your app Manifest, inside your tags
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="#string/default_notification_channel_id"/>
<activity android:name=".NotificationActivity"
android:theme="#style/myDialog"> </activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/notification_icon"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/color_accent" />
6- add these lines in your Application.java onCreate() method, or in MainActivity.class onCreate() method:
// notifications channel creation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String channelId = getResources().getString("default_channel_id");
String channelName = getResources().getString("General announcements");
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_LOW));
}
Done.
Now for this to work well in the 3 mentioned scenarios, you have to send the notification from the Firebase web console in the following way:
In the Notification section:
Notification Title = Title to display in the notification dialog (optional)
Notification text = Message to show to the user (required)
Then in the Target section:
App = your Android app
and in Additional Options section:
Android Notification Channel = default_channel_id
Custom Data
key: title value: (same text here than in the Title field of the Notification section)
key: body value: (same text here than in the Message field of the Notification section)
key:click_action value: .MainActivity
Sound=Disabled
Expires=4 weeks
You can debug it in the Emulator with API 28 with Google Play.
Happy coding!
According to docs
Handle messages in a backgrounded app
When your app is in the background, Android directs notification
messages to the system tray. A user tap on the notification opens the
app launcher by default.
This includes messages that contain both notification and data
payload. In these cases, the notification is delivered to the device's
system tray, and the data payload is delivered in the extras of the
intent of your launcher Activity.
If you want to open your app and perform a specific action, set
click_action in the notification payload and map it to an intent
filter in the Activity you want to launch. For example, set
click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the
following:
<intent-filter> <action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Edit :
Based on this thread :
You can't set click_action payload using Firebase Console. You could try testing with a curl command or a custom http server
curl --header "Authorization: key=<YOUR_KEY_GOES_HERE>"
--header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send
-d "{\"to\":\"/topics/news\",\"notification\":
{\"title\": \"Click Action Message\",\"text\": \"Sample message\",
\"click_action\":\"OPEN_ACTIVITY_1\"}}"
To capture the message in background you need to use a BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.legacy.content.WakefulBroadcastReceiver
import com.google.firebase.messaging.RemoteMessage
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
val TAG: String = FirebaseBroadcastReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val dataBundle = intent.extras
if (dataBundle != null)
for (key in dataBundle.keySet()) {
Log.d(TAG, "dataBundle: " + key + " : " + dataBundle.get(key))
}
val remoteMessage = RemoteMessage(dataBundle)
}
}
and add this to your manifest:
<receiver
android:name="MY_PACKAGE_NAME.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
Since the display-messages which are sent from Firebase Notification UI only works if your app is in foreground. For data-messages, there is a need to make a POST call to FCM
Steps
Install Advanced Rest Client Google Chrome Extension
Add the following headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key="your server key"
Add the body
If using topics :
{
"to" : "/topics/topic_name",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
If using registration id :
{
"registration_ids" : "[{"id"},{id1}]",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
Thats it!. Now listen to onMessageReceived callback as usual.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String value1 = data.get("key1");
String value2 = data.get("key2");
}
According to the docs: May 17, 2017
When your app is in the background, Android
directs notification messages to the system tray. A user tap on the
notification opens the app launcher by default.
This includes messages that contain both notification and data payload
(and all messages sent from the Notifications console). In these
cases, the notification is delivered to the device's system tray, and
the data payload is delivered in the extras of the intent of your
launcher Activity.
So,you should use both of the payload notification + data:
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
There is no need to use click_action.You should just get exras from intent on LAUNCHER activity
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Java code should be on onCreate method on MainActivity :
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
Bundle extras = intent.getExtras();
String someData= extras.getString("someData");
String someData2 = extras.getString("someData2");
}
You can test both of the payload notification + data from Firebase Notifications Console . Don't forget to fill custom data fields on Advanced options section
Here is more clear concepts about firebase message. I found it from their support team.
Firebase has three message types:
Notification messages : Notification message works on background or foreground. When app is in background, Notification messages are delivered to the system tray. If the app is in the foreground, messages are handled by onMessageReceived() or didReceiveRemoteNotification callbacks. These are essentially what is referred to as Display messages.
Data messages: On Android platform, data message can work on background and foreground. The data message will be handled by onMessageReceived(). A platform specific note here would be: On Android, the data payload can be retrieved in the Intent used to launch your activity. To elaborate, if you have "click_action":"launch_Activity_1", you can retrieve this intent through getIntent() from only Activity_1.
Messages with both notification and data payloads: When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification. When in the foreground, your app receives a message object with both payloads available. Secondly, the click_action parameter is often used in notification payload and not in data payload. If used inside data payload, this parameter would be treated as custom key-value pair and therefore you would need to implement custom logic for it to work as intended.
Also, I recommend you to use onMessageReceived method (see Data message) to extract the data bundle. From your logic, I checked the bundle object and haven't found expected data content. Here is a reference to a similar case which might provide more clarity.
For more info visit my this thread
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
}
is not called every time it is called only when app is in forground
there is one override method this method is called every time , no matter what app is in foreground or in background or killed but this method is available with this firebase api version
this is the version u have to import from gradle
compile 'com.google.firebase:firebase-messaging:10.2.1'
this is the method
#Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
// you can get ur data here
//intent.getExtras().get("your_data_key")
}
with previous firebase api this method was not there so in that case
fire base handle itself when app is in background .... now u have this method
what ever u want to do ... u can do it here in this method .....
if you are using previous version than default activity will start
in that case u can get data same way
if(getIntent().getExtras() != null && getIntent().getExtras().get("your_data_key") != null) {
String strNotificaiton = getIntent().getExtras().get("your_data_key").toString();
// do what ever u want ....
}
generally this is the structure from server we get in notification
{
"notification": {
"body": "Cool offers. Get them before expiring!",
"title": "Flat 80% discount",
"icon": "appicon",
"click_action": "activity name" //optional if required.....
},
"data": {
"product_id": 11,
"product_details": "details.....",
"other_info": "......."
}
}
it's up to u how u want to give that data key or u want give notification anything u can give .......
what ever u will give here with same key u will get that data .........
there are few cases if u r not sending click action in that case when u will click on notification default activity will open , but if u want to open your specific activity when app is in background u can call your activity from this on handleIntent method because this is called every time
Simple summary like this
if your app is running;
onMessageReceived()
is triggers.
if your app is not running (killed by swiping) ;
onMessageReceived()
is not triggered and delivered by direclty. If you have any specialy key-value pair. They don' t work beacuse of onMessageReceived() not working.
I' ve found this way;
In your launcher activity, put this logic,
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.activity_splash);
if (getIntent().getExtras() != null && getIntent().getExtras().containsKey("PACKAGE_NAME")) {
// do what you want
// and this for killing app if we dont want to start
android.os.Process.killProcess(android.os.Process.myPid());
} else {
//continue to app
}
}
in this if block, search for your keys according to firebase UI.
In this example my key and value like above; (sorry for language =))
When my code works, i get "com.rda.note".
android.os.Process.killProcess(android.os.Process.myPid());
with this line of code, i closed my application and open Google Play Market
happy coding =)
2017 updated answer
Here is a clear-cut answer from the docs regarding this:
I figured out the scenarios,
When app is in foreground,
onMessageReceived() method is called from the FirebaseService.So the pendingIntent defined in the service class will be called.
And when app is in background, first activity is called.
Now, if you use a splash activity, then must keep in mind the splashactivity will be called, else if there is no splashActivity, then whatever the first activity is, will be called.
Then you need to check getIntent() of the firstActivity to see if it has any bundle .if everything is alright you will see bundle is there with values filled in. If the value in data tag sent from server looks like this,
"data": {
"user_name": "arefin sajib",
"value": "user name notification"
}
Then in the first activity, you will see,
there is a valid intent( getIntent() is not null) , valid bundle and inside bundle , there will the whole JSON mentioned above with data as key.
For this scenario, code for extraction of value will look like this,
if(getIntent()!=null){
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
try {
JSONObject object = new JSONObject(bundle.getStringExtra("data"));
String user_name = object.optString("user_name");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
I had same problem. After some digging why my MainActivity is called with intent without data I realized that my LAUNCHER activity (as in Manifest) is SplashActivity. There I found the message data and forwarded them to MainActivity. Works like sharm. I beleive this can help someone.
Thanks for all another answers.
Remove notification payload completely from your server request. Send only data and handle it in onMessageReceived(), otherwise your onMessageReceived will not be triggered when the app is in background or killed.
Here is what I am sending from server:
{
"data":{
"id": 1,
"missedRequests": 5
"addAnyDataHere": 123
},
"to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......"
}
So you can receive your data in onMessageReceived(RemoteMessage message) like this: (let's say I have to get the id)
Object obj = message.getData().get("id");
if (obj != null) {
int id = Integer.valueOf(obj.toString());
}
And similarly you can get any data which you have sent from server within onMessageReceived().
Thanks to All of you for your Answers. But I solved this by sending data message instead of sending Notification.
Server code
<?php
$url = "https://fcm.googleapis.com/fcm/send";
$token = "C-l6T_a7HouUK****";
$serverKey = "AAAAaOcKS00:********";
define( 'API_ACCESS_KEY', $serverKey );
$registrationIds = array($token);
// prep the bundle
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'subtitle' => 'This is a subtitle. subtitle',
'tickerText' => 'Ticker text here...Ticker text here...Ticker text
here',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send'
);
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
?>
And caught the Data in onMessageReceived
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("message"));
}
// Check if message contains a notification payload.
else if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
sendNotification(remoteMessage.getNotification().getBody());
}
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, Notify.class).putExtra("msg",messageBody);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = "idddd";
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(MyFirebaseMessagingService.this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("FCM Message")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
In general
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
Data Messages example:
{
"to": "/path",
"data":
{
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Display Messages example:
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
Android side can handle notifications like:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
…
#Override public void onMessageReceived(RemoteMessage remoteMessage){
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
}
…
}
More details about FCM you can find here : Set up a Firebase Cloud Messaging client app on Android
I added the following code in the firebase-messaging-sw.js,
messaging.onBackgroundmessage((payload)=>{
console.log("background message detected!!");
console.log("message : ", payload);
})
this was triggered every time message received in background. but I was unable to use the payload in the main thread as SW didn't supported it. so then I researched a lot and found a solution in an Android forum.
so the solution is that we will have to remove the notification payload from the request payload.
so I changed my payload from
{
"notification": {
"title": "Hey there",
"body": "Subscribe to AMAL MOHAN N youtube channel"
},
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
to
{
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
change in the payload automatically made the receiveMessage() trigger in foreground messages as well as background messages.
I found this in an Android forum and this worked for me! please let me know if this works for you.
The easy way to send messages even if the app is in background and foreground as follow:-
To send a message using API, you can use a tool called AdvancedREST Client, its a chrome extension, and send a message with the following parameters.
Rest client tool Link: https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo
use this url:- https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=Your Server key From or Authoization key(see below ref)
{ "data": {
"image": "https://static.pexels.com/photos/4825/red-love-romantic-flowers.jpg",
"message": "Firebase Push Message Using API"
"AnotherActivity": "True"
},
"to" : "device id Or Device token"
}
Authorization key can be obtained by visiting Google developers console and click on Credentials button on the left menu for your project. Among the API keys listed, the server key will be your authorization key.
And you need to put tokenID of the receiver in the “to” section of your POST request sent using API.
you want to work onMessageReceived(RemoteMessage remoteMessage) in background send only data part notification part this:
"data": "image": "", "message": "Firebase Push Message Using API",
"AnotherActivity": "True", "to" : "device id Or Device token"
By this onMessageRecivied is call background and foreground no need to handle notification using notification tray on your launcher activity.
Handle data payload in using this:
public void onMessageReceived(RemoteMessage remoteMessage)
if (remoteMessage.getData().size() > 0)
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
The solutions provided didn't work in my case.
At the end of the day, I figured out that some battery optimization apps give users the control to allow or block apps from sending notifications. In my case it was Samsung's Smart Manager that was automatically blocking my app as soon as it was killed/cleaned from the recents list.
Turning off that feature for my app was the only solution I could find.
To be able to retrieve data from firebase notification sent when app is in background, you need to add click_action entry in your notification data set.
Set additional options of your notification in firebase console like this: (you have to include any extra data that you want to retrieve in your app here):
And include the intent filter in your manifest file under the activity to be launched
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.MyApp.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<intent-filter>
<action android:name="FIREBASE_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Then get the bundle data in your activity onNewIntent:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle data = intent.getExtras();
if (data != null) {
for (String key : data.keySet()) {
Object value = data.get(key);
// do what you want with the data entries
Log.d(FIREBASE_TAG, "Key: " + key + " Value: " + value);
Toast.makeText(this, "Key: "+key+".... Value: "+value, Toast.LENGTH_LONG).show;
}
}
}
When your app is in foreground, you can set your onMessageReceived like this:
#Override
public void onMessageReceived(#NonNull RemoteMessage message) {
Log.d(FIREBASE_TAG, "Message From: " + message.getFrom());
if (message.getNotification() != null) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Map<String, String> data = message.getData();
if(data != null && !data.isEmpty()){
for(Map.Entry<String ,String > entry : data.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
}
//.......
// implement the rest of the code to show notification
//
}
}
June 2018 Answer -
You have to make sure there is not a "notification" keyword anywhere in the message. Only include "data", and the app will be able to handle the message in onMessageReceived, even if in background or killed.
Using Cloud Functions:
const message = {
token: token_id, // obtain device token id by querying data in firebase
data: {
title: "my_custom_title",
body: "my_custom_body_message"
}
}
return admin.messaging().send(message).then(response => {
// handle response
});
Then in your onMessageReceived(), in your class extending com.google.firebase.messaging.FirebaseMessagingService :
if (data != null) {
Log.d(TAG, "data title is: " + data.get("title");
Log.d(TAG, "data body is: " + data.get("body");
}
// build notification using the body, title, and whatever else you want.
According to OAUTH 2.0:
There will be Auth problem for this case beacuse FCM now using OAUTH 2
So I read firebase documentation and according to documentation new way to post data message is;
POST: https://fcm.googleapis.com/v1/projects/YOUR_FIREBASEDB_ID/messages:send
Headers
Key: Content-Type, Value: application/json
Auth
Bearer YOUR_TOKEN
Example Body
{
"message":{
"topic" : "xxx",
"data" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
In the url there is Database Id which you can find it on your firebase console. (Go project setttings)
And now lets take our token (It will valid only 1 hr):
First in the Firebase console, open Settings > Service Accounts. Click Generate New Private Key, securely store the JSON file containing the key. I was need this JSON file to authorize server requests manually. I downloaded it.
Then I create a node.js project and used this function to get my token;
var PROJECT_ID = 'YOUR_PROJECT_ID';
var HOST = 'fcm.googleapis.com';
var PATH = '/v1/projects/' + PROJECT_ID + '/messages:send';
var MESSAGING_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging';
var SCOPES = [MESSAGING_SCOPE];
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
getAccessToken().then(function(accessToken) {
console.log("TOKEN: "+accessToken)
})
});
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./YOUR_DOWNLOADED_JSON_FILE.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
Now I can use this token in my post request. Then I post my data message, and it is now handled by my apps onMessageReceived function.
Since 2019, Google Firebase has a big change in their APIs
I mean:
'com.google.firebase:firebase-messaging:18.0.0'
in 18.0.0 they removed MyFirebaseInstanceIDService and you need to get token in MyFirebaseMessagingService so you just need to write :
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
}
and also in your AndroidManifest.xml, you have to remove :
<service android:name=".service.MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Also, you're recommended to set default values to customize the appearance of notifications. You can specify a custom default icon and a custom default color that are applied whenever equivalent values are not set in the notification payload.
Add these lines inside the application tag to set the custom default icon and custom color:
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorAccent" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/push_channel" />
now to handle notification messages in a backgrounded app you should define an Intent in your first Activity even if it is SplashScreen, When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.
for example, if your Json is like this:
"data": {
"message": "2",
"title": "1",
"pushType" : "banner",
"bannerLink": "http://www.google.com",
"image" : "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"}
you just need to write a simple intent to get those values:
Bundle extras = intent.getExtras();
String bannerLink = extras.getString("bannerLink");
...
String channelId = extras.getString("channelId");
In addition to above answers,
If you are testing push notifications using FCM console, 'data' key and object is not added to Push Notification bundle. So you will not receive detailed push notification when App is background or killed.
In this case you have to opt for your back end admin console to test App background scenario.
Here, you will have added 'data' key to your push bundle. so, detailed push will be shown as expected.
Hope this helps few.
Using this code you can get the notification in background/foreground and also put action:
//Data should come in this format from the notification
{
"to": "/xyz/Notifications",
"data": {
"key1": "title notification",
"key2": "description notification"
}
}
In-App use this code:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String key1Data = remoteMessage.getData().get("key1");
// use key1Data to according to your need
}
I solved this issue by using Broadcast Message.
create an Broadcast and send the payload in broadcast Message from the service worker.
then Receive the payload inside application and handle it the way you want.
This case is only for firebase admin notification
If the application is in the background FirebaseMessagingService does not get triggered
For handling this case go to launcher activity and check for the intent bundle which is attached, and print out all data using this code :
intent?.extras?.let {it->
for (key in bundle.keySet()) {
val value = bundle[key]
Log.d("NotificationData", String.format("%s %s (%s)", key, value.toString(), value!!.javaClass.name))
}
}
check-in log all data with data types is displayed ;
Example :
I want to get the Week and title from notifications will use this code
intent?.extras?.let {it->
if (it.containsKey("week")){
}
if (it.containsKey("title")){
}
Here is my manifest:
<service android:name=".fcm.PshycoFirebaseMessagingServices">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".fcm.PshycoFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
When the app is in the background and a notification arrives, then the default notification comes and doesn't run my code of onMessageReceived.
Here is my onMessageReceived code. This is invoked if my app is running on the foreground, not when it is running in the background. How can I run this code when the app is in background too?
// [START receive_message]
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
// If the application is in the foreground handle both data and notification messages here.
// 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.
data = remoteMessage.getData();
String title = remoteMessage.getNotification().getTitle();
String message = remoteMessage.getNotification().getBody();
String imageUrl = (String) data.get("image");
String action = (String) data.get("action");
Log.i(TAG, "onMessageReceived: title : "+title);
Log.i(TAG, "onMessageReceived: message : "+message);
Log.i(TAG, "onMessageReceived: imageUrl : "+imageUrl);
Log.i(TAG, "onMessageReceived: action : "+action);
if (imageUrl == null) {
sendNotification(title,message,action);
} else {
new BigPictureNotification(this,title,message,imageUrl,action);
}
}
// [END receive_message]
1. 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
NOTE: Firebase team have not developed a UI to send data-messages to
your devices, yet. You should use your server for sending this type!
2. How to?
To achieve this, you have to perform a POST request to the following URL:
POST https://fcm.googleapis.com/fcm/send
Headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key=<your-server-key>
Body using topics
{
"to": "/topics/my_topic",
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Or if you want to send it to specific devices
{
"data": {
"my_custom_key": "my_custom_value",
"my_custom_key2": true
},
"registration_ids": ["{device-token}","{device2-token}","{device3-token}"]
}
NOTE: Be sure you're not adding JSON key notification
NOTE: To get your server key, you can find it in the firebase console: Your project -> settings -> Project settings -> Cloud messaging -> Server Key
3. How to handle the push notification message?
This is how you handle the received message:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
// Manage data
}
To make firebase library to call your onMessageReceived() in the following cases
App in foreground
App in background
App has been killed
you must not put JSON key notification in your request to Firebase API but instead, use data, see below.
The following message will not call your onMessageReceived() when your app is in the background or killed, and you can't customize your notification.
{
"to": "/topics/journal",
"notification": {
"title" : "title",
"text": "data!",
"icon": "ic_notification"
}
}
but instead using this will work
{
"to": "/topics/dev_journal",
"data": {
"text":"text",
"title":"",
"line1":"Journal",
"line2":"刊物"
}
}
Basically, the message is sent in the argument RemoteMessage along with your data object as Map<String, String>, then you can manage the notification in onMessageReceived as in the snippet here
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
//you can get your text message here.
String text= data.get("text");
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
// optional, this is to make beautiful icon
.setLargeIcon(BitmapFactory.decodeResource(
getResources(), R.mipmap.ic_launcher))
.setSmallIcon(smallIcon) //mandatory
.......
/*You can read more on notification here:
https://developer.android.com/training/notify-user/build-notification.html
https://www.youtube.com/watch?v=-iog_fmm6mE
*/
}
I feel like all the responses are incomplete but all of them have something that you need to process a notification that has data when your app is in the background.
Follow these steps and you will be able to process your notifications when your app is in the background.
Add an intent-filter like this:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
to an activity that you want to process the notification data.
Send notifications with the next format:
{
"notification" : {
"click_action" : ".MainActivity",
"body" : "new Symulti update !",
"title" : "new Symulti update !",
"icon" : "ic_notif_symulti" },
"data": { ... },
"to" : "c9Vaa3ReGdk:APA91bH-AuXgg3lDN2WMcBrNhJZoFtYF9" }
The key here is add
"click_action" : ".MainActivity"
where .MainActivity is the activity with the intent-filter that you added in step 1.
Get data info from notification in the onCreate of .MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get notification data info
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
//bundle must contain all info sent in "data" field of the notification
}
}
And that should be all you need to do.
According to the firebase documentation in send downstream using firebase, there is 2 type of payload :
data
This parameter specifies the custom key-value pairs of the message's payload.
Client app is responsible for processing data messages. Data messages have only custom key-value pairs.
notification
This parameter specifies the predefined, user-visible key-value pairs of the notification payload. FCM automatically displays the message to end-user devices on behalf of the client app. Notification messages have a predefined set of user-visible keys.
When you are in the foreground you can get the data inside FCM using onMessageReceived(), you can get your data from data payload.
data = remoteMessage.getData();
String customData = (String) data.get("customData");
When you are in background, FCM will showing notification in system tray based on the info from notification payload. Title, message, and icon which used for the notification on system tray are get from the notification payload.
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
This notification payload are used when you want to automactically showing notification on the system tray when your app is in the background.
To get notification data when your app in the background, you should add click_action inside notification payload.
If you want to open your app and perform a specific action [while backgrounded], set click_action in the notification payload and map it to an intent filter in the Activity you want to launch. For example, set click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the following:
<intent-filter>
<action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Put that intent-filter on your manifest, inside one of your activity tag. When you click the notification, it will open the app and go straight to activity that you define in click_action, in this case "OPEN_ACTIVTY_1".
And inside that activity you can get the data by :
Bundle b = getIntent().getExtras();
String someData = b.getString("someData");
I'm using FCM for my android app and use both of the payload.
Here is the example JSON i'm using :
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
Working as of July 2019
Android compileSdkVersion 28, buildToolsVersion 28.0.3 and firebase-messaging:19.0.1
After many many hours of researching through all of the other StackOverflow questions and answers, and trying innumerable outdated solutions, this solution managed to show notifications in these 3 scenarios:
- App is in foreground:
the notification is received by the onMessageReceived method at my MyFirebaseMessagingService class
- App has been killed (it is not running in background):
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is launched by calling the activity that has android.intent.category.LAUNCHER in the manifest. You can get the data part of the notification by using getIntent().getExtras() at the onCreate() method.
- App is in background:
the notification is sent to the notification tray automatically by FCM. When the user touches the notification the app is brought to the foreground by launching the activity that has android.intent.category.LAUNCHER in the manifest. As my app has launchMode="singleTop" in that activity, the onCreate() method is not called because one activity of the same class is already created, instead the onNewIntent() method of that class is called and you get the data part of the notification there by using intent.getExtras().
Steps:
1- If you define your app's main activity like this:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:largeHeap="true"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter>
<action android:name=".MainActivity" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2- add these lines at the onCreate() method of your MainActivity.class
Intent i = getIntent();
Bundle extras = i.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onCreate: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
and these methods to the same MainActivity.class:
#Override
public void onNewIntent(Intent intent){
//called when a new intent for this class is created.
// The main case is when the app was in background, a notification arrives to the tray, and the user touches the notification
super.onNewIntent(intent);
Log.d(Application.APPTAG, "onNewIntent - starting");
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(Application.APPTAG, "Extras received at onNewIntent: Key: " + key + " Value: " + value);
}
String title = extras.getString("title");
String message = extras.getString("body");
if (message!=null && message.length()>0) {
getIntent().removeExtra("body");
showNotificationInADialog(title, message);
}
}
}
private void showNotificationInADialog(String title, String message) {
// show a dialog with the provided title and message
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
3- create the class MyFirebase like this:
package com.yourcompany.app;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
public MyFirebaseMessagingService() {
super();
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(Application.APPTAG, "myFirebaseMessagingService - onMessageReceived - message: " + remoteMessage);
Intent dialogIntent = new Intent(this, NotificationActivity.class);
dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra("msg", remoteMessage);
startActivity(dialogIntent);
}
}
4- create a new class NotificationActivity.class like this:
package com.yourcompany.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ContextThemeWrapper;
import com.google.firebase.messaging.RemoteMessage;
public class NotificationActivity extends AppCompatActivity {
private Activity context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
Bundle extras = getIntent().getExtras();
Log.d(Application.APPTAG, "NotificationActivity - onCreate - extras: " + extras);
if (extras == null) {
context.finish();
return;
}
RemoteMessage msg = (RemoteMessage) extras.get("msg");
if (msg == null) {
context.finish();
return;
}
RemoteMessage.Notification notification = msg.getNotification();
if (notification == null) {
context.finish();
return;
}
String dialogMessage;
try {
dialogMessage = notification.getBody();
} catch (Exception e){
context.finish();
return;
}
String dialogTitle = notification.getTitle();
if (dialogTitle == null || dialogTitle.length() == 0) {
dialogTitle = "";
}
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.myDialog));
builder.setTitle(dialogTitle);
builder.setMessage(dialogMessage);
builder.setPositiveButton(getResources().getString(R.string.accept), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
5- Add these lines to your app Manifest, inside your tags
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="#string/default_notification_channel_id"/>
<activity android:name=".NotificationActivity"
android:theme="#style/myDialog"> </activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/notification_icon"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/color_accent" />
6- add these lines in your Application.java onCreate() method, or in MainActivity.class onCreate() method:
// notifications channel creation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
String channelId = getResources().getString("default_channel_id");
String channelName = getResources().getString("General announcements");
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_LOW));
}
Done.
Now for this to work well in the 3 mentioned scenarios, you have to send the notification from the Firebase web console in the following way:
In the Notification section:
Notification Title = Title to display in the notification dialog (optional)
Notification text = Message to show to the user (required)
Then in the Target section:
App = your Android app
and in Additional Options section:
Android Notification Channel = default_channel_id
Custom Data
key: title value: (same text here than in the Title field of the Notification section)
key: body value: (same text here than in the Message field of the Notification section)
key:click_action value: .MainActivity
Sound=Disabled
Expires=4 weeks
You can debug it in the Emulator with API 28 with Google Play.
Happy coding!
According to docs
Handle messages in a backgrounded app
When your app is in the background, Android directs notification
messages to the system tray. A user tap on the notification opens the
app launcher by default.
This includes messages that contain both notification and data
payload. In these cases, the notification is delivered to the device's
system tray, and the data payload is delivered in the extras of the
intent of your launcher Activity.
If you want to open your app and perform a specific action, set
click_action in the notification payload and map it to an intent
filter in the Activity you want to launch. For example, set
click_action to OPEN_ACTIVITY_1 to trigger an intent filter like the
following:
<intent-filter> <action android:name="OPEN_ACTIVITY_1" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Edit :
Based on this thread :
You can't set click_action payload using Firebase Console. You could try testing with a curl command or a custom http server
curl --header "Authorization: key=<YOUR_KEY_GOES_HERE>"
--header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send
-d "{\"to\":\"/topics/news\",\"notification\":
{\"title\": \"Click Action Message\",\"text\": \"Sample message\",
\"click_action\":\"OPEN_ACTIVITY_1\"}}"
To capture the message in background you need to use a BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.legacy.content.WakefulBroadcastReceiver
import com.google.firebase.messaging.RemoteMessage
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
val TAG: String = FirebaseBroadcastReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val dataBundle = intent.extras
if (dataBundle != null)
for (key in dataBundle.keySet()) {
Log.d(TAG, "dataBundle: " + key + " : " + dataBundle.get(key))
}
val remoteMessage = RemoteMessage(dataBundle)
}
}
and add this to your manifest:
<receiver
android:name="MY_PACKAGE_NAME.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
Since the display-messages which are sent from Firebase Notification UI only works if your app is in foreground. For data-messages, there is a need to make a POST call to FCM
Steps
Install Advanced Rest Client Google Chrome Extension
Add the following headers
Key: Content-Type, Value: application/json
Key: Authorization, Value: key="your server key"
Add the body
If using topics :
{
"to" : "/topics/topic_name",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
If using registration id :
{
"registration_ids" : "[{"id"},{id1}]",
"data": {
"key1" : "value1",
"key2" : "value2",
}
}
Thats it!. Now listen to onMessageReceived callback as usual.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
String value1 = data.get("key1");
String value2 = data.get("key2");
}
According to the docs: May 17, 2017
When your app is in the background, Android
directs notification messages to the system tray. A user tap on the
notification opens the app launcher by default.
This includes messages that contain both notification and data payload
(and all messages sent from the Notifications console). In these
cases, the notification is delivered to the device's system tray, and
the data payload is delivered in the extras of the intent of your
launcher Activity.
So,you should use both of the payload notification + data:
{
"to": "FCM registration ID",
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification"
},
"data": {
"someData" : "This is some data",
"someData2" : "etc"
}
}
There is no need to use click_action.You should just get exras from intent on LAUNCHER activity
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Java code should be on onCreate method on MainActivity :
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
Bundle extras = intent.getExtras();
String someData= extras.getString("someData");
String someData2 = extras.getString("someData2");
}
You can test both of the payload notification + data from Firebase Notifications Console . Don't forget to fill custom data fields on Advanced options section
Here is more clear concepts about firebase message. I found it from their support team.
Firebase has three message types:
Notification messages : Notification message works on background or foreground. When app is in background, Notification messages are delivered to the system tray. If the app is in the foreground, messages are handled by onMessageReceived() or didReceiveRemoteNotification callbacks. These are essentially what is referred to as Display messages.
Data messages: On Android platform, data message can work on background and foreground. The data message will be handled by onMessageReceived(). A platform specific note here would be: On Android, the data payload can be retrieved in the Intent used to launch your activity. To elaborate, if you have "click_action":"launch_Activity_1", you can retrieve this intent through getIntent() from only Activity_1.
Messages with both notification and data payloads: When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification. When in the foreground, your app receives a message object with both payloads available. Secondly, the click_action parameter is often used in notification payload and not in data payload. If used inside data payload, this parameter would be treated as custom key-value pair and therefore you would need to implement custom logic for it to work as intended.
Also, I recommend you to use onMessageReceived method (see Data message) to extract the data bundle. From your logic, I checked the bundle object and haven't found expected data content. Here is a reference to a similar case which might provide more clarity.
For more info visit my this thread
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
}
is not called every time it is called only when app is in forground
there is one override method this method is called every time , no matter what app is in foreground or in background or killed but this method is available with this firebase api version
this is the version u have to import from gradle
compile 'com.google.firebase:firebase-messaging:10.2.1'
this is the method
#Override
public void handleIntent(Intent intent) {
super.handleIntent(intent);
// you can get ur data here
//intent.getExtras().get("your_data_key")
}
with previous firebase api this method was not there so in that case
fire base handle itself when app is in background .... now u have this method
what ever u want to do ... u can do it here in this method .....
if you are using previous version than default activity will start
in that case u can get data same way
if(getIntent().getExtras() != null && getIntent().getExtras().get("your_data_key") != null) {
String strNotificaiton = getIntent().getExtras().get("your_data_key").toString();
// do what ever u want ....
}
generally this is the structure from server we get in notification
{
"notification": {
"body": "Cool offers. Get them before expiring!",
"title": "Flat 80% discount",
"icon": "appicon",
"click_action": "activity name" //optional if required.....
},
"data": {
"product_id": 11,
"product_details": "details.....",
"other_info": "......."
}
}
it's up to u how u want to give that data key or u want give notification anything u can give .......
what ever u will give here with same key u will get that data .........
there are few cases if u r not sending click action in that case when u will click on notification default activity will open , but if u want to open your specific activity when app is in background u can call your activity from this on handleIntent method because this is called every time
Simple summary like this
if your app is running;
onMessageReceived()
is triggers.
if your app is not running (killed by swiping) ;
onMessageReceived()
is not triggered and delivered by direclty. If you have any specialy key-value pair. They don' t work beacuse of onMessageReceived() not working.
I' ve found this way;
In your launcher activity, put this logic,
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.activity_splash);
if (getIntent().getExtras() != null && getIntent().getExtras().containsKey("PACKAGE_NAME")) {
// do what you want
// and this for killing app if we dont want to start
android.os.Process.killProcess(android.os.Process.myPid());
} else {
//continue to app
}
}
in this if block, search for your keys according to firebase UI.
In this example my key and value like above; (sorry for language =))
When my code works, i get "com.rda.note".
android.os.Process.killProcess(android.os.Process.myPid());
with this line of code, i closed my application and open Google Play Market
happy coding =)
2017 updated answer
Here is a clear-cut answer from the docs regarding this:
I figured out the scenarios,
When app is in foreground,
onMessageReceived() method is called from the FirebaseService.So the pendingIntent defined in the service class will be called.
And when app is in background, first activity is called.
Now, if you use a splash activity, then must keep in mind the splashactivity will be called, else if there is no splashActivity, then whatever the first activity is, will be called.
Then you need to check getIntent() of the firstActivity to see if it has any bundle .if everything is alright you will see bundle is there with values filled in. If the value in data tag sent from server looks like this,
"data": {
"user_name": "arefin sajib",
"value": "user name notification"
}
Then in the first activity, you will see,
there is a valid intent( getIntent() is not null) , valid bundle and inside bundle , there will the whole JSON mentioned above with data as key.
For this scenario, code for extraction of value will look like this,
if(getIntent()!=null){
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
try {
JSONObject object = new JSONObject(bundle.getStringExtra("data"));
String user_name = object.optString("user_name");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
I had same problem. After some digging why my MainActivity is called with intent without data I realized that my LAUNCHER activity (as in Manifest) is SplashActivity. There I found the message data and forwarded them to MainActivity. Works like sharm. I beleive this can help someone.
Thanks for all another answers.
Remove notification payload completely from your server request. Send only data and handle it in onMessageReceived(), otherwise your onMessageReceived will not be triggered when the app is in background or killed.
Here is what I am sending from server:
{
"data":{
"id": 1,
"missedRequests": 5
"addAnyDataHere": 123
},
"to": "fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......"
}
So you can receive your data in onMessageReceived(RemoteMessage message) like this: (let's say I have to get the id)
Object obj = message.getData().get("id");
if (obj != null) {
int id = Integer.valueOf(obj.toString());
}
And similarly you can get any data which you have sent from server within onMessageReceived().
Thanks to All of you for your Answers. But I solved this by sending data message instead of sending Notification.
Server code
<?php
$url = "https://fcm.googleapis.com/fcm/send";
$token = "C-l6T_a7HouUK****";
$serverKey = "AAAAaOcKS00:********";
define( 'API_ACCESS_KEY', $serverKey );
$registrationIds = array($token);
// prep the bundle
$msg = array
(
'message' => 'here is a message. message',
'title' => 'This is a title. title',
'subtitle' => 'This is a subtitle. subtitle',
'tickerText' => 'Ticker text here...Ticker text here...Ticker text
here',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'registration_ids' => $registrationIds,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send'
);
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
?>
And caught the Data in onMessageReceived
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("message"));
}
// Check if message contains a notification payload.
else if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
sendNotification(remoteMessage.getNotification().getBody());
}
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, Notify.class).putExtra("msg",messageBody);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = "idddd";
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(MyFirebaseMessagingService.this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("FCM Message")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
In general
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
Data Messages example:
{
"to": "/path",
"data":
{
"my_custom_key": "my_custom_value",
"my_custom_key2": true
}
}
Display Messages example:
{
"notification": {
"title" : "title",
"body" : "body text",
"icon" : "ic_notification",
"click_action" : "OPEN_ACTIVITY_1"
}
}
Android side can handle notifications like:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
…
#Override public void onMessageReceived(RemoteMessage remoteMessage){
Map<String, String> data = remoteMessage.getData();
String myCustomKey = data.get("my_custom_key");
}
…
}
More details about FCM you can find here : Set up a Firebase Cloud Messaging client app on Android
I added the following code in the firebase-messaging-sw.js,
messaging.onBackgroundmessage((payload)=>{
console.log("background message detected!!");
console.log("message : ", payload);
})
this was triggered every time message received in background. but I was unable to use the payload in the main thread as SW didn't supported it. so then I researched a lot and found a solution in an Android forum.
so the solution is that we will have to remove the notification payload from the request payload.
so I changed my payload from
{
"notification": {
"title": "Hey there",
"body": "Subscribe to AMAL MOHAN N youtube channel"
},
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
to
{
"to": "your-browser-token",
"data": {
"value1": "text",
"value2": "",
"value3": "sample3",
"value4": "sample4"
}
}
change in the payload automatically made the receiveMessage() trigger in foreground messages as well as background messages.
I found this in an Android forum and this worked for me! please let me know if this works for you.
The easy way to send messages even if the app is in background and foreground as follow:-
To send a message using API, you can use a tool called AdvancedREST Client, its a chrome extension, and send a message with the following parameters.
Rest client tool Link: https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo
use this url:- https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=Your Server key From or Authoization key(see below ref)
{ "data": {
"image": "https://static.pexels.com/photos/4825/red-love-romantic-flowers.jpg",
"message": "Firebase Push Message Using API"
"AnotherActivity": "True"
},
"to" : "device id Or Device token"
}
Authorization key can be obtained by visiting Google developers console and click on Credentials button on the left menu for your project. Among the API keys listed, the server key will be your authorization key.
And you need to put tokenID of the receiver in the “to” section of your POST request sent using API.
you want to work onMessageReceived(RemoteMessage remoteMessage) in background send only data part notification part this:
"data": "image": "", "message": "Firebase Push Message Using API",
"AnotherActivity": "True", "to" : "device id Or Device token"
By this onMessageRecivied is call background and foreground no need to handle notification using notification tray on your launcher activity.
Handle data payload in using this:
public void onMessageReceived(RemoteMessage remoteMessage)
if (remoteMessage.getData().size() > 0)
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
The solutions provided didn't work in my case.
At the end of the day, I figured out that some battery optimization apps give users the control to allow or block apps from sending notifications. In my case it was Samsung's Smart Manager that was automatically blocking my app as soon as it was killed/cleaned from the recents list.
Turning off that feature for my app was the only solution I could find.
To be able to retrieve data from firebase notification sent when app is in background, you need to add click_action entry in your notification data set.
Set additional options of your notification in firebase console like this: (you have to include any extra data that you want to retrieve in your app here):
And include the intent filter in your manifest file under the activity to be launched
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.MyApp.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<intent-filter>
<action android:name="FIREBASE_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Then get the bundle data in your activity onNewIntent:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle data = intent.getExtras();
if (data != null) {
for (String key : data.keySet()) {
Object value = data.get(key);
// do what you want with the data entries
Log.d(FIREBASE_TAG, "Key: " + key + " Value: " + value);
Toast.makeText(this, "Key: "+key+".... Value: "+value, Toast.LENGTH_LONG).show;
}
}
}
When your app is in foreground, you can set your onMessageReceived like this:
#Override
public void onMessageReceived(#NonNull RemoteMessage message) {
Log.d(FIREBASE_TAG, "Message From: " + message.getFrom());
if (message.getNotification() != null) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Map<String, String> data = message.getData();
if(data != null && !data.isEmpty()){
for(Map.Entry<String ,String > entry : data.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
}
//.......
// implement the rest of the code to show notification
//
}
}
June 2018 Answer -
You have to make sure there is not a "notification" keyword anywhere in the message. Only include "data", and the app will be able to handle the message in onMessageReceived, even if in background or killed.
Using Cloud Functions:
const message = {
token: token_id, // obtain device token id by querying data in firebase
data: {
title: "my_custom_title",
body: "my_custom_body_message"
}
}
return admin.messaging().send(message).then(response => {
// handle response
});
Then in your onMessageReceived(), in your class extending com.google.firebase.messaging.FirebaseMessagingService :
if (data != null) {
Log.d(TAG, "data title is: " + data.get("title");
Log.d(TAG, "data body is: " + data.get("body");
}
// build notification using the body, title, and whatever else you want.
According to OAUTH 2.0:
There will be Auth problem for this case beacuse FCM now using OAUTH 2
So I read firebase documentation and according to documentation new way to post data message is;
POST: https://fcm.googleapis.com/v1/projects/YOUR_FIREBASEDB_ID/messages:send
Headers
Key: Content-Type, Value: application/json
Auth
Bearer YOUR_TOKEN
Example Body
{
"message":{
"topic" : "xxx",
"data" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
In the url there is Database Id which you can find it on your firebase console. (Go project setttings)
And now lets take our token (It will valid only 1 hr):
First in the Firebase console, open Settings > Service Accounts. Click Generate New Private Key, securely store the JSON file containing the key. I was need this JSON file to authorize server requests manually. I downloaded it.
Then I create a node.js project and used this function to get my token;
var PROJECT_ID = 'YOUR_PROJECT_ID';
var HOST = 'fcm.googleapis.com';
var PATH = '/v1/projects/' + PROJECT_ID + '/messages:send';
var MESSAGING_SCOPE = 'https://www.googleapis.com/auth/firebase.messaging';
var SCOPES = [MESSAGING_SCOPE];
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
getAccessToken().then(function(accessToken) {
console.log("TOKEN: "+accessToken)
})
});
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./YOUR_DOWNLOADED_JSON_FILE.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
SCOPES,
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
Now I can use this token in my post request. Then I post my data message, and it is now handled by my apps onMessageReceived function.
Since 2019, Google Firebase has a big change in their APIs
I mean:
'com.google.firebase:firebase-messaging:18.0.0'
in 18.0.0 they removed MyFirebaseInstanceIDService and you need to get token in MyFirebaseMessagingService so you just need to write :
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
}
and also in your AndroidManifest.xml, you have to remove :
<service android:name=".service.MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
Also, you're recommended to set default values to customize the appearance of notifications. You can specify a custom default icon and a custom default color that are applied whenever equivalent values are not set in the notification payload.
Add these lines inside the application tag to set the custom default icon and custom color:
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/colorAccent" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/push_channel" />
now to handle notification messages in a backgrounded app you should define an Intent in your first Activity even if it is SplashScreen, When your app is in the background, Android directs notification messages to the system tray. A user tap on the notification opens the app launcher by default.
for example, if your Json is like this:
"data": {
"message": "2",
"title": "1",
"pushType" : "banner",
"bannerLink": "http://www.google.com",
"image" : "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"}
you just need to write a simple intent to get those values:
Bundle extras = intent.getExtras();
String bannerLink = extras.getString("bannerLink");
...
String channelId = extras.getString("channelId");
In addition to above answers,
If you are testing push notifications using FCM console, 'data' key and object is not added to Push Notification bundle. So you will not receive detailed push notification when App is background or killed.
In this case you have to opt for your back end admin console to test App background scenario.
Here, you will have added 'data' key to your push bundle. so, detailed push will be shown as expected.
Hope this helps few.
Using this code you can get the notification in background/foreground and also put action:
//Data should come in this format from the notification
{
"to": "/xyz/Notifications",
"data": {
"key1": "title notification",
"key2": "description notification"
}
}
In-App use this code:
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String key1Data = remoteMessage.getData().get("key1");
// use key1Data to according to your need
}
I solved this issue by using Broadcast Message.
create an Broadcast and send the payload in broadcast Message from the service worker.
then Receive the payload inside application and handle it the way you want.
This case is only for firebase admin notification
If the application is in the background FirebaseMessagingService does not get triggered
For handling this case go to launcher activity and check for the intent bundle which is attached, and print out all data using this code :
intent?.extras?.let {it->
for (key in bundle.keySet()) {
val value = bundle[key]
Log.d("NotificationData", String.format("%s %s (%s)", key, value.toString(), value!!.javaClass.name))
}
}
check-in log all data with data types is displayed ;
Example :
I want to get the Week and title from notifications will use this code
intent?.extras?.let {it->
if (it.containsKey("week")){
}
if (it.containsKey("title")){
}