1

I'm making a messaging app with Firestore and I want to send notifications when a new message is sent. For this I'm using FCM.

Right now I am declaring 2 payloads: a notification payload and a data payload. When a user sends a message the recipient gets 2 notifications if the app is in the background, and one notification if the app is in the foreground. If I remove the notification payload, the recipient gets one notification whether the app is in the foreground or background.

What I want to happen is that the user receives just one notification when the app is in the background or foreground, and none if the app is in the chat activity with the sender. My understanding is this should be the default behaviour, when I declare both notification and data payloads.

Handle notification 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 (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.

Can anybody help? I have read this and this but it just doesnt seem to work for me so maybe there is something else I'm not aware of here is my manifest entry

    <service
        android:name=".MyFirebaseMessagingService">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT"/>
        </intent-filter>
    </service>
    <service
        android:name=".MyFirebaseInstanceIDService">
        <intent-filter>
            <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
        </intent-filter>
    </service>

Here is my cloud function with both notification and data payloads

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.notifyNewMessage = 
functions.firestore
.document('users/{ID}/contacts/{contactID}/messages/{messageID}')
.onCreate((docSnapshot , context) => {
const message = docSnapshot.data()
const recipientId = message['recipientId']
const senderId = message['sender']
const senderName = message['senderName']

return admin.firestore().doc('users/' + recipientId).get().then(userDoc =>{
    const registrationTokens = userDoc.get('registeredToken')
    const notificationBody = (message['data_type'] === "TEXT") ? 
    message['message'] : "You received a new image message."
    const payload = {
        notification : {
            title : senderName + " sent you a message.",
            body : notificationBody
        },
        data : {
            TITLE : senderName + " sent you a message.",
            BODY : notificationBody,
            USER_NAME : senderName,
            USER_NUMBER : message['sender'],
            CLICK_ACTION : "MessageListActivity",
            MESSAGE : message['message'],
        }
    }

    return 
    admin.messaging().sendToDevice(registrationTokens,payload).then(response 
     => {


    })
  })
})

And here is my messaging service where I send the notification very basic for now

public class MyFirebaseMessagingService extends FirebaseMessagingService {


private static final int NOTIFICATION_MAX_CHARACTERS = 30;

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Map<String, String> data = remoteMessage.getData();
        if (data.size() > 0) {
            sendNotification(data);
        }
    }
    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {

    }
}

private void sendNotification(Map<String, String> data) {

    String author = data.get("USER_NAME");
    String title = data.get("TITLE");
    String body = data.get("BODY");
    String id = data.get("USER_NUMBER");
    String message = data.get("MESSAGE");

    Intent intent = new Intent(this, MessageListActivity.class);
    intent.putExtra(Constants.USER_NAME, author);
    intent.putExtra(Constants.USER_NUMBER, id);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    // Create the pending intent to launch the activity
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
            PendingIntent.FLAG_ONE_SHOT);
    if (message.length() > NOTIFICATION_MAX_CHARACTERS) {
        message = message.substring(0, NOTIFICATION_MAX_CHARACTERS) + 
        "\u2026";
    }

    Uri defaultSoundUri = 
    RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    NotificationCompat.Builder notificationBuilder = new 
    NotificationCompat.Builder(this)
            .setSmallIcon(R.drawable.ic_email_black_24dp)

   .setContentTitle(String.format(getString(R.string.notification_message), 
   author))
            .setContentText(message)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent);

    NotificationManager notificationManager =
            (NotificationManager) 
    getSystemService(Context.NOTIFICATION_SERVICE);

    notificationManager.notify(0 /* ID of notification */, 
    notificationBuilder.build());
  }
}
martinseal1987
  • 1,862
  • 8
  • 44
  • 77

2 Answers2

2

If you want to avoid showing a notification you should control it on your FirebaseMessagingService. Your condition looks to be that your application is open in an specific activity.

If you wanna avoid notifications when the app is not in the Foreground, then you should check the ActivityManager from the Service. You can use this method for it:

public boolean isForeground(String myPackage) {
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> runningTaskInfo = manager.getRunningTasks(1); 
    ComponentName componentInfo = runningTaskInfo.get(0).topActivity;
    return componentInfo.getPackageName().equals(myPackage);
}

Also add <uses-permission android:name="android.permission. REAL_GET_TASKS" /> to your manifest.

In case you wanna avoid it just in an specific activity. Then you need to figure out a way to keep track of the lifetime of that activity. Using SharedPreferences, a static variable, you can see multiple examples in this post.

halfer
  • 19,824
  • 17
  • 99
  • 186
Francisco Durdin Garcia
  • 12,540
  • 9
  • 53
  • 95
1

Given your use case, I think you need to communicate to your FCM message service the state of your chat activity and decide whether to present a notification based on that. Then you can send only the data portion with the payload.

You can try setting up a broadcast receiver in your FCM service and broadcast intents from your chat activity lifecycle events to notify the service whether a notification should be generated.

In your message service:

private boolean chatActive = false;
private String senderID;

@Override
public void onCreate() {
    super.onCreate();
    LocalBroadcastManager.getInstance(this).registerReceiver(chatReceiver,new IntentFilter("com.mypackage.CHAT_LIFECYCLE_EVENT"));
}

@Override
public void onDestroy() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(chatReceiver);
    super.onDestroy();
}


private BroadcastReceiver chatReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        chatActive = intent.getBooleanExtra("active",false);
        senderID = intent.getStringExtra("sender");
    }
};

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    //Check for whether chat is active with sender
    if(!(chatActive && remoteMessage.getData().get("USER_NAME").equals(senderID)){
    //sendNotification
    }
}

Then in your chat activity you can send the broadcasts during lifecycle events.

@Override
protected void onPause() {
    super.onPause();
    Intent startNotificationIntent = new Intent();
    startNotificationIntent.setAction("com.mypackage.CHAT_LIFECYCLE_EVENT");
    startNotificationIntent.putExtra("active",false);
    LocalBroadcastManager.getInstance(this).sendBroadcast(startNotificationIntent);
}

@Override
protected void onResume() {
    super.onResume();
    Intent stopNotificationIntent = new Intent();
    stopNotificationIntent.setAction("com.mypackage.CHAT_LIFECYCLE_EVENT");
    stopNotificationIntent.putExtra("active",true);
    stopNotificationIntent.putExtra("sender",someSenderVariable);
    LocalBroadcastManager.getInstance(this).sendBroadcast(stopNotificationIntent);
}
Cody W.
  • 376
  • 2
  • 6
  • WOW, ok i had already resigned myself to using just the data payload and refusing notifications based on the chat activity but didnt know how i was going to check who was using it, this will work perfectly thank you very much – martinseal1987 May 31 '18 at 15:21
  • After more thought, I am concerned that this won't work as expected. If the FCM service is created when the chat activity is active it won't receive the broadcast. If you find this to be true, you can try doing this with static variables instead like in Francisco's answer. – Cody W. May 31 '18 at 16:18