0

I am trying to use FCM to send UpStream Message, so I followed the tutorial on google and it works.

As shown in the code below in MainActivity, I send Upstream message when the button is clicked, then in MyAndroidFirebaseMsgService I should see a Log message as shown below in MyAndroidFirebaseMsgService.

But what happen is, the Log messages in MyAndroidFirebaseMsgService in onMessageSent in do not get displayed even I kept pressing the button several times. the Log message in MyAndroidFirebaseMsgService in onMessageSent can be displayed only if sent a downstream messagefrom FCM to the App, in this case, both the Logs in in MyAndroidFirebaseMsgService will be displayed.

Please let me know why the Log message in onMessageSent is not getting displayed once there is an UpStream message sent?and how to fix it.

Mainactivity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mBtnSendUpstreamMsg = (Button) findViewById(R.id.btn_send_upstream_message);
    mBtnSendUpstreamMsg.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            FirebaseMessaging fm = FirebaseMessaging.getInstance();
            fm.send(new RemoteMessage.Builder("673xxxxx" + "@gcm.googleapis.com")
                    .setMessageId("2")
                    .addData("my_message", "Hello World")
                    .addData("my_action","SAY_HELLO")
                    .build());
        }
    });
}

MyAndroidFirebaseMsgService:

public class MyAndroidFirebaseMsgService extends FirebaseMessagingService {
private final static String TAG = MyAndroidFirebaseMsgService.class.getSimpleName();

@Override
public void onMessageSent(String s) {
    super.onMessageSent(s);
    Log.d(TAG, "onMessageSent: upstream message");
}

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    Log.d(TAG, "onMessageReceived: downstream message");
    //Log data to Log Cat
    Log.d(TAG, "onMessageReceived->From: " + remoteMessage.getFrom());
    Log.d(TAG, "onMessageReceived->Notification Message Body: " + remoteMessage.getNotification().getBody());
    //create notification
    createNotification(remoteMessage.getNotification().getBody());
}

private void createNotification( String messageBody) {
    Intent intent = new Intent( this , ResultActivity.class );
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    PendingIntent resultIntent = PendingIntent.getActivity( this , 0, intent,
            PendingIntent.FLAG_ONE_SHOT);

    Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder( this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Android Tutorial Point FCM Tutorial")
            .setContentText(messageBody)
            .setAutoCancel( true )
            .setSound(notificationSoundURI)
            .setContentIntent(resultIntent);

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

    notificationManager.notify(0, mNotificationBuilder.build());
}

}

KENdi
  • 7,576
  • 2
  • 16
  • 31
Amrmsmb
  • 1
  • 27
  • 104
  • 226
  • Possible duplicate of [Firebase Cloud Messaging onMessageSent not called](https://stackoverflow.com/questions/37471510/firebase-cloud-messaging-onmessagesent-not-called) – Bob Snyder Jul 20 '17 at 14:25
  • The answer and comments for the question I referenced indicate that because of a bug in Firebase, `onMessageSent()` is not invoked unless the `RemoteMessage` is built with `setTtl()`. I didn't observe this with 11.0.2. Note that there is a delay of ~20 minutes before `onMessageSent()` is called. This is [explained in the docs](https://firebase.google.com/docs/cloud-messaging/android/upstream#handle_upstream_message_callbacks): _To optimize network usage, FCM batches responses to onMessageSent and onSendError, so the ack may not be immediate for each message_ – Bob Snyder Jul 20 '17 at 15:19
  • but the link you provided does not answer my question, because in the link you provided i should use `RemoteMessage` and it is not defined in my code..any idea how to import/use `RemoteMessage`class – Amrmsmb Jul 20 '17 at 15:25
  • I updated my comment. You should not need to `setTtl()` if you are using version 11.0.2. Are you waiting 20 minutes for `onMessageSent()` to be called? – Bob Snyder Jul 20 '17 at 15:28
  • i am using 10.0.1....compile 'com.google.firebase:firebase-messaging:10.0.1' compile 'com.google.android.gms:play-services:10.0.1' – Amrmsmb Jul 20 '17 at 15:30
  • You may need to use `setTtl()`. It is a method of `RemoteMessage.Builder`. You can add it to your builder, for example after `setMessageId("2")`. – Bob Snyder Jul 20 '17 at 15:33

1 Answers1

0

Yes, is possible to send a Firebase messaging push notification and receive it in all app life cycles using onMessageReceived.

But is necessary to change the default Firebase behaviour, intercepting the intent request before everything else.


** IMPORTANT NOTE **

This was a pretty stupid idea from Firebase by remove the developers processment capability when the FCM message arives with the notification message format, but not for data message.

This created a bunch of "workarounds" in many solutions, which made the analythics and everything else being messed up.

If I had designed this solution, I would always call the onMessageReceived method with a completion handle. Let the developer decide what to do (free tip for you, Firebase).

Use onMessageReceived is the correct way to do. This method is the only one who brings RemoteMessage object, that have every information what you need. It was designed for it. You are on correct path.


** HOW TO DO **

In your Firebase Class MyAndroidFirebaseMsgService, which extends FirebaseMessagingService, override the public method handleIntent to intercep the intent request before Firebase catch it.

    @Override
    public void handleIntent(Intent intent){

        if(intent.hasExtra("google.message_id")){
            intent = handleFirebaseIntent(intent);
        }

        super.handleIntent(intent);
    }

After, transform the notification message package into an data message, removing all "gcm.notification.%" and "gcm.n.%" extras from intent, and translating "gcm.notification.title", "gcm.notification.body" and "gcm.notification.image" elements into what you need:

    // Thank you Google, for that brilliant idea to treat notification message and notification data
    // differently on Android, depending of what app life cycle is. Because of that, all the developers
    // are doing "workarounds", using data to send push notifications, and that's not what you planned for.
    // Let the developers decide what to do on their apps and ALWAYS deliver the notification
    // to "onMessageReceived" method. Its simple, is freedom and its what the creative ones need.
    private Intent handleFirebaseIntent(Intent intent){

        //printIntentExtras(intent);

        String FCM_TITLE_KEY = "gcm.notification.title";
        String FCM_BODY_KEY = "gcm.notification.body";
        String FCM_IMAGE_KEY = "gcm.notification.image";

        String title = intent.getStringExtra(FCM_TITLE_KEY);
        String body = intent.getStringExtra(FCM_BODY_KEY);
        String image = intent.getStringExtra(FCM_IMAGE_KEY);

        // Remove the key extras that identifies an Notification type message
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            for (String key : bundle.keySet()) {
                if (key.startsWith("gcm.notification.") || key.startsWith("gcm.n."))
                {
                    intent.removeExtra(key);
                }
            }
        }

        Boolean isTitleEmpty = StringUtils.isNullOrEmpty(title);
        Boolean isBodyEmpty = StringUtils.isNullOrEmpty(body);
        Boolean isImageEmpty = StringUtils.isNullOrEmpty(image);

        // Notification title and body has prevalence over Data title and body
        if(
            !isTitleEmpty || !isBodyEmpty || !isImageEmpty
        ){

            // This is my personalized translation method, designed for my solution.
            // Probably you gonna need to do it by your own

            String contentData = intent.getStringExtra(Definitions.PUSH_NOTIFICATION_CONTENT);

            Map<String, Object> content;
            if(StringUtils.isNullOrEmpty(contentData)){

                content = new HashMap<String, Object>();

                content.put(Definitions.NOTIFICATION_ID, new Random().nextInt(65536) - 32768);
                content.put(Definitions.NOTIFICATION_CHANNEL_KEY, "basic_channel" );

            } else {
                content = JsonUtils.fromJson(new TypeToken<Map<String, Object>>(){}.getType(),contentData);
            }

            if(!isTitleEmpty) content.put(Definitions.NOTIFICATION_TITLE, title);
            if(!isBodyEmpty) content.put(Definitions.NOTIFICATION_BODY, body);
            if(!isImageEmpty){
                content.put(Definitions.NOTIFICATION_BIG_PICTURE, image);
                content.put(Definitions.NOTIFICATION_LAYOUT, NotificationLayout.BigPicture.toString());
            }

            contentData = JsonUtils.toJson(content);
            intent.putExtra(Definitions.PUSH_NOTIFICATION_CONTENT, contentData);
        }

        //printIntentExtras(intent);

        return intent;
    }

    private void printIntentExtras(Intent intent){
        Bundle bundle;
        if ((bundle = intent.getExtras()) != null) {
            for (String key : bundle.keySet()) {
                System.out.println(key + " : " + (bundle.get(key) != null ? bundle.get(key) : "NULL"));
            }
        }
    }

You can check my entire solution here.

Rafael Setragni
  • 160
  • 1
  • 6