Cordova-plugin-firebase: (Android) Click on notification doesn´t trigger onNotificationOpen callback

Created on 21 Sep 2016  ·  28Comments  ·  Source: arnesson/cordova-plugin-firebase

Cordova 6.3.1
Phonegap firebase plugin 0.1.12
Device: LG G5 with Android 6.0.1

This is the code I am using:
`var app = {
// Application Constructor
initialize: function () {
this.bindEvents();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
// deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function () {
console.log ("Estamos en onDeviceReady");
if (navigator.connection.type == Connection.NONE) {
navigator.notification.alert('Se requiere conexión a Internet');
} else {
window.FirebasePlugin.onNotificationOpen(function(notification) {
console.log(notification);
navigator.notification.alert ("Recibido");
}, function(error) {
console.log(error);
});
}
},

};
app.initialize();`

I receive notifications in the tray of system in all the situations: app in background, foreground and closed and when I click on notification, app is open in the three cases but callback is not triggered.

Is there anything wrong in the code?

Thanks in advance.

Most helpful comment

I have re-written the whole onNotificationOpen handling and have it almost working as I would expect, which is:

  • Notification arrives with client in forground, open notification and onNotificationOpen is called in the current client (without reload)
  • Notification arrives with client in background, open notification and client is brought into forground and onNotificationOpen is called with current client (without reload)

The only thing remaining is to have the notification delivered immediately to the client without needing to be opened, if that's at all possible (haven't looked into it yet).

I am a bit of an Android noob, so there may be some improvements that can be made, but it seems to work ok, so thought I would share.

OnNotificationOpenReceiver:onReceive now simply calls

FirebasePlugin.onBroadcastReceive(context, intent);

Changed FirebasePlugin as follows

  • remove WeakReference from callbackContext
  • add onBroadcastReceive method, passes intent data to onNotificationOpen
  • add onNewIntent method, passes intent data to onNotificationOpen
  • restored old version of onNotificationOpen and changed it as follows

    • removed WeakReference line

    • Change callbacks to use a PluginResult and call setKeepCallback(true) on the result to prevent the callback from being removed after the first call

    private static CallbackContext callbackContext;
    // ...
    private void registerOnNotificationOpen(final CallbackContext callbackContext) {
        FirebasePlugin.callbackContext = callbackContext;
    }

    // called when in foreground
    public static void onBroadcastReceive(Context context, Intent intent) {
        Log.d("FirebasePlugin", "onBroadcastReceive");
        Bundle data = intent.getExtras();
        FirebasePlugin.onNotificationOpen(data);
    }

    // called when in background
    @Override
    public void onNewIntent(Intent intent) {
        Log.d(TAG, "new intent " + intent);
        super.onNewIntent(intent);
        FirebasePlugin.onNotificationOpen(intent.getExtras());
    }

    public static void onNotificationOpen(Bundle bundle) {
        if (FirebasePlugin.callbackContext == null ) {
            Log.d("FirebasePlugin", "no callback context, onNotificationOpen ignored");
            return;
        }
        if (callbackContext != null && bundle != null) {
            JSONObject json = new JSONObject();
            Set<String> keys = bundle.keySet();
            for (String key : keys) {
                try {
                    json.put(key, bundle.get(key));
                } catch (JSONException e) {
                    Log.d("FirebasePlugin", "onNotificationOpen: json exception");
                    PluginResult result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
                    result.setKeepCallback(true);
                    callbackContext.sendPluginResult(result);
                    return;
                }
            }
            Log.d("FirebasePlugin", "onNotificationOpen: send notification to javascript");
            PluginResult result = new PluginResult(PluginResult.Status.OK, json);
            result.setKeepCallback(true);
            callbackContext.sendPluginResult(result);
        }
    }

All 28 comments

same problem, on android notifications arrives but inside the app no callback is called.
v. 0.1.12

@voidbrain @Kibukita please, test the latest version from the repo. I tried to improved the open notification. Thanks.

@BugsBunnyBR I just tried updating to the latest version of the repo and just tested it. On Android its still not executing the onNotificationOpen.

@superheroben , could you provide a repo with a sample of how you are calling the plugin code? How are you sending the notifications? I tested with topic messages.

Same problem here.
getInstanceId() callback is called, notification arrives, but the onNotificationOpen() is never called.

Client Code:

if (window.FirebasePlugin)
{
  window.FirebasePlugin.getInstanceId(
    function(token) {
        thiss.saveToken(token);
    }, 
    function(error) {
        console.log(error);
    }
  );

  window.FirebasePlugin.onNotificationOpen(
    function(notification) {                  
      alert("Yeah!!!");                  
    }, 
    function(error) {
      alert("Error!");
      console.error(error);
    }
  );

  window.FirebasePlugin.grantPermission();
}

My notification data structure:

(
    [to] => (device token)
    [notification] => Array
        (
            [title] => Title
            [text] => Test message
            [sound] => default
            [vibrate] => 1
            [badge] => 2
        )
)

My server side code (PHP):

$jsonData = json_encode($data);

$ch     = curl_init("https://fcm.googleapis.com/fcm/send");
$header = array(
    'Content-Type: application/json',
    "Authorization: key=".MY_GCM_API_KEY
);

curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, true);
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );

curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);

$result = curl_exec($ch);
curl_close($ch);

Working on Cordova Android 5.2.2. The device used to test runs Android 4.4.2. Plugin version 0.1.12.

@arivanbastos , have you tried pointing to the github repo?

Installing from github partially solved the problem. Thank you.

cordova plugin remove cordova-plugin-firebase
cordova plugin add https://github.com/arnesson/cordova-plugin-firebase/

Now the onNotificationOpen() callback is called when the app is in background.
When the app is in foreground, rather than documentation says, the notification arrives and the onNotificationOpen() is not called:

App is in foreground:
User receives the notification data in the JavaScript callback without any notification on the device itself (this is the normal behaviour of push notifications, it is up to you, the developer, to notify the user)

If you send a push of the "notification" type (without a data body) the notification shouldn`t be displayed when the app is in foreground.

Sorry, I didn't understand. Should I just add a "data" section to my notification data?

(
    [to] => (device token)
    [notification] => Array
        (
            [title] => Title
            [text] => Test message
        )
    [data] => Array
        (
            [test] => 1
        )
)

This did not fix the problem.

Renaming the "notification" section by "data" makes the app crash ("App has stopped") when notification arrives:

(
    [to] => (device token)
    [data] => Array
        (
            [title] => Title
            [text] => Test message
        )
)

https://firebase.google.com/docs/cloud-messaging/android/receive
Here it is described very well.
Your Notification goes to the System-Tray if the app is in the background and to the onMessageReceived() if the app is in foreground.
If your message contains data then this is always passed to the onMessageReceived().

I think that there is a problem in the implementation of onMessageReceived().
When I look to the code I interpret is as follow:

  • extract data
  • building a notification with NotificationBuilder
  • notify via NotificationManager

NotificationManager.notify(id, notification) posts the notification to be shown in the Status Bar.

However, for me this seems to be expected that everytime I receive a message from FCM then it will be shown a notification in the Status Bar.
I don't see any check if the App is in foreground and I don't see any call to the callback. I am no Android developer so maybe I am wrong but the described behaviour fits to that.

OK, I found the piece of code that calls the callback.
With the last commit from BugsBunnyBR there was change in the OnNotificationOpenReceiver which explains that arivanbastos gets his callback executed when pointing to the github repo.

But still, the notification gets only send (to the OnNotificationOpenReceiver and the NotificationManager) if there is either a text or title (in notification or in data). That means it is not possible to send data to your app without getting the notification send to the NotificationManager which shows it in the StatusBar.

@arivanbastos
I said something wrong to you. Sorry about that.
Have you tried pointing to this repo version?

@packowitz and @robertarnesson . Ok, my implementation will ALWAYS* try to show the notification. Or the Firebase automatic displayed notification or the one build inside onMessageReceived will be displayed.
The onNotificationOpen callback will be called when onMessageReceived is called or when the notification has Data and Notification bodies at the same time. In my pr that introduced the onNotificationOpen I tried to explain that push notifications of Notification type would not fire the callback. The recommendation is to always include a Data body and Notification body, so the plugin can detect and fire the callback.

I know that, always showing the notification is not the standard Firebase notification behavior.

Most Android app developers want their notification to be shown in the sys-tray even If the app is in foreground. I know there are cases, in like chat apps, that this behavior is not a good thing.
When I developed the Android notification feature I did not care about chat apps needs or to comply with Firebase.

What can be done is:
1) Develop a flag to be set when the app is open saying "hey, I want you to show the notification even when the app is open" and use it to control the behavior. It wouldn`t need to be persisted in any storage if the app always set the flag when opening.

2) Just comment this line and disable the notification when the app is in foreground.

_Always_ -> If the plugin finds a "text" or "title" in the notification body.

@BugsBunnyBR I like the idea of having a flag to choose the behaviour.
I would decouple the NotificationManager from the OnNotificationOpenReceiver calling back your JS-callback. My recommendation is to introduce a check, if there is data in the message and if so then call the callback with the data.
For the notification it would be good to have the flag.
Thank you.

@BugsBunnyBR I just tested to switch to the github repo and found that tapping on a notification restarts my app even if it is already running. Happens even when the app is in foreground and i tap on the notification.
But the callback works now ;)

Same here, for me the version 0.1.13 is causing my app restart just when it is in foreground. Version 0.1.12 is working just fine.

Notifications are just not working sensibly in the repo version. The reason is I think, if onNotificationOpen is called before a notification has been opened, it is ignored.

https://github.com/arnesson/cordova-plugin-firebase/blob/master/src/android/FirebasePlugin.java#L123

In registerOnNotificationOpen it only registers the callback IF there is a notificationBundle waiting

Also, the reason the app seem to reload if already open is in OnNotificationOpenReceiver it explicitly says

launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);

https://github.com/arnesson/cordova-plugin-firebase/blob/master/src/android/OnNotificationOpenReceiver.java#L17

which forces the client to reload, upon where the notificationBundle is available when onNotificationOpen callback is registered so the client does then get the notification.

I have re-written the whole onNotificationOpen handling and have it almost working as I would expect, which is:

  • Notification arrives with client in forground, open notification and onNotificationOpen is called in the current client (without reload)
  • Notification arrives with client in background, open notification and client is brought into forground and onNotificationOpen is called with current client (without reload)

The only thing remaining is to have the notification delivered immediately to the client without needing to be opened, if that's at all possible (haven't looked into it yet).

I am a bit of an Android noob, so there may be some improvements that can be made, but it seems to work ok, so thought I would share.

OnNotificationOpenReceiver:onReceive now simply calls

FirebasePlugin.onBroadcastReceive(context, intent);

Changed FirebasePlugin as follows

  • remove WeakReference from callbackContext
  • add onBroadcastReceive method, passes intent data to onNotificationOpen
  • add onNewIntent method, passes intent data to onNotificationOpen
  • restored old version of onNotificationOpen and changed it as follows

    • removed WeakReference line

    • Change callbacks to use a PluginResult and call setKeepCallback(true) on the result to prevent the callback from being removed after the first call

    private static CallbackContext callbackContext;
    // ...
    private void registerOnNotificationOpen(final CallbackContext callbackContext) {
        FirebasePlugin.callbackContext = callbackContext;
    }

    // called when in foreground
    public static void onBroadcastReceive(Context context, Intent intent) {
        Log.d("FirebasePlugin", "onBroadcastReceive");
        Bundle data = intent.getExtras();
        FirebasePlugin.onNotificationOpen(data);
    }

    // called when in background
    @Override
    public void onNewIntent(Intent intent) {
        Log.d(TAG, "new intent " + intent);
        super.onNewIntent(intent);
        FirebasePlugin.onNotificationOpen(intent.getExtras());
    }

    public static void onNotificationOpen(Bundle bundle) {
        if (FirebasePlugin.callbackContext == null ) {
            Log.d("FirebasePlugin", "no callback context, onNotificationOpen ignored");
            return;
        }
        if (callbackContext != null && bundle != null) {
            JSONObject json = new JSONObject();
            Set<String> keys = bundle.keySet();
            for (String key : keys) {
                try {
                    json.put(key, bundle.get(key));
                } catch (JSONException e) {
                    Log.d("FirebasePlugin", "onNotificationOpen: json exception");
                    PluginResult result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
                    result.setKeepCallback(true);
                    callbackContext.sendPluginResult(result);
                    return;
                }
            }
            Log.d("FirebasePlugin", "onNotificationOpen: send notification to javascript");
            PluginResult result = new PluginResult(PluginResult.Status.OK, json);
            result.setKeepCallback(true);
            callbackContext.sendPluginResult(result);
        }
    }

Humm... I have noticed notifications are not delivered (to the device) when the client isn't running (has been killed).

Try to open a notification presented by firebase directly and see the behavior.. I guess they restart the main activity which I think is the right behavior.

Sorry for the delay. I tried with 0.1.13 but my app is restarted after notification is open.

any news ?

I'm unable to get any onNotificationOpen callback success or fail on android using 0.1.17. Neither sending through the API or GUI with a payload works. Any suggestions?

Why dont we create a pull request with that code @Mehuge posted ?It works like charm on android for me.

I had to update onNewIntent in FirebasePlugin to filter out normal launch intents, so my onNewIntent code now looks like this

@Override
public void onNewIntent(Intent intent) {
  Log.d(TAG, "new intent " + intent);
  super.onNewIntent(intent);
  Bundle data = intent.getExtras();
  if (data != null) {
    String id = data.getString("id");
    if (null != id) {
      FirebasePlugin.handleNotificationBundle(data);
    } else {
      Log.d(TAG, "Not a notification intent, ignored");
    }
  }
}

I am not entirely happy with it. It works by looking for an id property in the intent bundle which my server code always sends. There doesn't seem to be anything that GCM or FBM reliably add to the bundle that can be used to identify the intent as a notification/message. Sometimes we get some google properties added (if we are opening a notification from the tray) but for messages and notifications delivered directly to the client when it's in foreground, there is nothing other than the data in the message that tells you it's a launch intent because of a notification, at least that I could see.

There is probably a better way to handle this.

@Mehuge would you mind creating gists with all files you have?

Ok here it is https://gist.github.com/Mehuge/374ee24d9e18a6c7ccc171d3e521b7ad

Note however, that there are a few bits in there that are specific to our app. I ended up moving the code into our project because I was changing it about so much. In hindsight, I should probably have forked the plugin and modified it that way, but by this point, I was way behind schedule and quite fed up with the whole thing, so I took the quickest route to getting something working. The custom bits are fairly obvious, so should be easy enough to exclude.

Note also that the way I have implemented the plugin, it needs to know when the client is paused (or more specifically not paused) so it can decide if to create a notification or deliver the message directly. You may or may not need that functionality, but in our case, we didn't want notifications that were sent when the client was in the foreground to create android notifications but instead be delivered directly to the client to handle. To inform the plugin of the pause state of the client, add the following code to your main activity.

@Override
protected void onResume() {
    FirebasePlugin.setPaused(false);
    super.onResume();
}

@Override
protected void onPause() {
    FirebasePlugin.setPaused(true);
    super.onPause();
}

There is another issue that you may have to contend with, which is if a notification arrives while in the background but the user launches the app directly rather than by opening the android notification you may need to deal with that in some way. In our apps case, I could just clear any unopened notifications. Your situation may be different.

I am not entirely happy with the final result, some of the extra keys added for example were experimental and are not actually being used, I just have not got around to removing them.

Would be interested in any feedback or suggested improvements or pointing out any flaws. I would be particularly interested in finding a way to better detect a launch intent with a GCM payload from a normal launch. I found the google properties are only added in some circumstances. Also my attempts at detecting the different types of notifications (is_push, is_notify, broadcast) are not really working.

see #108

For me, I could resolve making the request like this:

{
  "registration_ids": [...tokens],
  "notification" : {
        "title": "Notf title",
        "body": "Notification body"
     },
     "data": {
        "click_action": "/call/dwEugLJ9PTVdcFb064CX"
     }
}

But I had to take the click_action as a parameter and do the redirect manually (I was using cordova with react app).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

danielpalen picture danielpalen  ·  5Comments

DanielAccorsi picture DanielAccorsi  ·  3Comments

merbin2012 picture merbin2012  ·  4Comments

rlz picture rlz  ·  4Comments

ulisesvera picture ulisesvera  ·  5Comments