EddyVerbruggen / nativescript-plugin-firebase

:fire: NativeScript plugin for Firebase
https://firebase.google.com
MIT License
1.01k stars 445 forks source link

Background Messages Android #518

Closed bpeterman closed 4 years ago

bpeterman commented 7 years ago

@EddyVerbruggen were you ever able to get background messages to come through? Digging through the code I don't see why it wouldn't work.

To be more specific the issue I'm seeing is when sending a data message (as described here https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) it doesn't seem to be invoking the onMessageReceived() method when the app is in the background.

Any details you can recall as you were implementing this would be helpful as I'm trying to implement a fix for this.

smarza commented 6 years ago

I'm having the same issue.

The onMessageReceivedCallback() is only get called if the APP is running in focus.

richarddavenport commented 6 years ago

I have this working currently, in both the init callback and the firebase.addOnMessageReceivedCallback(). Here is my manifest (notice the two services)

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android" package="__PACKAGE__" android:versionCode="1" android:versionName="1.0">
    <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true"/>
    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="__APILEVEL__"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@style/AppTheme">
        <activity android:name="com.tns.NativeScriptActivity" android:label="@string/title_activity_kimera" android:configChanges="keyboardHidden|orientation|screenSize" android:theme="@style/LaunchScreenTheme">
            <meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter android:priority="999">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </activity>
        <activity android:name="com.tns.ErrorReportActivity"/>
        <service android:name="org.nativescript.plugins.firebase.MyFirebaseInstanceIDService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
            </intent-filter>
        </service>
        <service android:name="org.nativescript.plugins.firebase.MyFirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>

    </application>
    <service android:name=".SmsRadarService" android:exported="false" android:label="@string/app_name"/>
</manifest>

I use postman to make my call for testing, here is my request:

POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Content-Type: application/json
Authorization: key={{SERVER_KEY_FROM_FIREBASE}}

{
  "notification" : {
    "body" : "great match!",
    "title" : "Portugal vs. Denmark",
    "icon" : "myicon"
  },
  "data" : {
    "foo": "hola!"
  },
  "to": "{{TOKEN_FROM_DEVICE}}"
}

Don't forget to add the FCM library in your include.gradle file. You need to go into the node_modules/nativescript-plugin-firebase/platforms/android/include.gradle file and uncomment it. Or you can add it in your app/App_Resources/Android/app.gradle file like this:

dependencies {
  compile "com.google.firebase:firebase-messaging:11.2.2"
}

If it's still not working, sometimes I clean out the platforms folder and reinstall the plugins and go through their respective install process. I know that can be time consuming, but it's fixed errors for me in the past.

bpeterman commented 6 years ago

@richarddavenport you're sending a notification with a data payload not a data message as described here: https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages. This issue is for data messages sent in the background.

richarddavenport commented 6 years ago

Yeah, sorry for the confusion. You're right, that is a notification message with the optional data payload. However it still works for me when sending an actual data message, like this:

{
  "data" : {
    "foo": "hola!"
  },
  "to": "{{TOKEN_FROM_DEVICE}}"
}

Have you tried reinstalling?

Skintillion commented 6 years ago

Once you click the notification you get the payload of data.

image

https://firebase.google.com/docs/cloud-messaging/android/receive

NickIliev commented 6 years ago

+1 reproducing this issue using the latest versions of the plugin.

Steps to reproduce:

@EddyVerbruggen FYI the same issue is now also reproducible with push-plugin you can contact @lini @radeva from the plugin team for collaboration regarding this matter

EddyVerbruggen commented 6 years ago

@NickIliev @lini @radeva I just tested the Firebase plugin with the demo app on my Samsung S8 (running Android 7.0).

When I send a data-only notification you won't see the notification in the tray, but when you launch the app and register the onMessageReceived handler in the init function you will get its payload.

I ran this curl command (replace the to value with the registered device token):

// data-only
curl -X POST --header "Authorization: key=AAAA9SHtZvM:APA91bGoY0H2nS8GlzzypDXSiUkNY3nrti4st4WOUs_w1A0Rttcx31U90YGv-p3U4Oql-vh-FzZzWUUPEwl47uvwhI4tB5yz4wwzrJA2fVqLEKZpDU42AQppYnU2-dsURqkyc9sKcjay2egWbfyNK2b-G2JQCqrLVA" --Header "Content-Type: application/json" https://fcm.googleapis.com/fcm/send -d "{\"data\":{\"foo\":\"bar\"}, \"priority\": \"High\", \"to\": \"dXAstJr7Tkk:APA91bHgTFGPn6PRpOyGIiwmPaJBuGEtJLMGioZGa-9cLTJbjjuD43rbZjS_v89ZzIB78BniHiBsMwT-UwB20QDPM9EwnpvG2iHLD6VAyBWKoVX-FPLlRyMc-dIOsazThOhg3isyz18E\"}"

So the service that's part of the Firebase plugin (which is also part of the Push plugin, although it's slightly different) receives and remembers the notification until the app registers the handler, at which moment the pending notification is sent to the app. I think that's exactly what @richarddavenport also observed.

I also retested the other case, where you send a 'notification' payload: the notification pops up in the system when the app is killed/background (and invokes the onMessageReceived handler in case the app is in the foreground and the handler was registered by your app. I've used this command:

// notification & data
curl -X POST --header "Authorization: key=AAAA9SHtZvM:APA91bGoY0H2nS8GlzzypDXSiUkNY3nrti4st4WOUs_w1A0Rttcx31U90YGv-p3U4Oql-vh-FzZzWUUPEwl47uvwhI4tB5yz4wwzrJA2fVqLEKZpDU42AQppYnU2-dsURqkyc9sKcjay2egWbfyNK2b-G2JQCqrLVA" --Header "Content-Type: application/json" https://fcm.googleapis.com/fcm/send -d "{\"notification\":{\"title\": \"My title\", \"text\": \"My text\", \"badge\": \"1\", \"sound\": \"default\"}, \"data\":{\"foo\":\"bar\"}, \"priority\": \"High\", \"to\": \"fmt3SiVgJH4:APA91bFNvamGmOr_ELaa2oFPunF7PzVfjINe3Hdu5UyqJpfdisCXZUFLyVwISqu7j5Ff9yGh3iPrMHHg3rEXwMRpQE9oCG85YV5pQ1pGjIjpAOAJCU31RKIqGLC5bIrTHxk1Dz--cmGE\"}"

I'm curious to learn if the push plugin behaves in the same way so I plan to take a look at that one tomorrow, but to me, this behaviour is exactly as intended.

@NickIliev One thing I can think of that can be the reason why you didn't receive a notification with the Firebase plugin is that you may not have required the plugin before the app started. The push plugin is a bit more forgiving in that respect and I could refactor that, but I need users to do it for iOS anyway.

BTW In case anyone wonders why I pushed code for this issue: while I was testing, I found a nice piece of code in the Push plugin that I'm copying: notifications that were received when the app was killed always were flagged as foreground: true, but that's incorrect of course. I found a LifecycleCallbacks class in your repo that fixed that, so thanks! 😍

souly1 commented 6 years ago

Am I missing something? I'm not sure I've got the exact same issue but here is my case:

rashtao commented 6 years ago

+1

bzaruk commented 6 years ago

+1

spmamidi commented 6 years ago

Is it possible to send image?

mrazahasan commented 6 years ago

+1

rAnAaRsl commented 6 years ago

Anyone found any solution ?

cerireyhan commented 6 years ago

+1

bzaruk commented 6 years ago

@EddyVerbruggen - Actually the same thing is happens also on Dynamic Links - When user get click the dynamic link when the app in on background mode it opens the app but addOnDynamicLinkReceivedCallback function doesn't invoke.

hettiger commented 6 years ago

This might be related: #692

KkevinLi commented 6 years ago

Using the latest version of the plugin 6.2.0, I am still experiencing problems related to background messages. When I am in the foreground or my app is minimized (in recent apps tray), onMessageReceived() does get called. The problem comes when I swipe the app away as onMessageReceived() no longer gets called. My app was not forced stop (checked with adb).

I am using FCM data messages without notification, but I expected onMessageReceived() to be called even when I kill the app (not force close).

hackerunet commented 6 years ago

I'm also having the exact same issue, is there any solutions at sight?

danielpontoal commented 5 years ago

I found a solution!

I had this same problem: my app received the notifications and the addOnMessageReceivedCallback() was called but only if my app was opened/suspended, otherwise, the app was opened but didn't pass the data and the function was ignored. I added this require variable at app.js (in app folder).

const firebase = require("nativescript-plugin-firebase");

in typescript I believe is even simpler (sorry, not an expert):

require("nativescript-plugin-firebase");

Doing this the function addOnMessageReceivedCallback() will be called when the users click, even if the app is closed. It works for me, hope that helps.

EddyVerbruggen commented 5 years ago

Yep, that’s correct. You need to require the plugin for startup wiring to work. Same for iOS. The question is: how can I document this even better?

GeorgeRXia commented 5 years ago

@EddyVerbruggen When I open my app through the notification, my onMessageReceivedCallback is called, but it doesn't have the body value of the payload. I am running on Android 7.0.

If the app is running in the foreground it works fine, I am able to get the body.

Skintillion commented 5 years ago

Make sure this is in your Android Manifest

        <service android:name="org.nativescript.plugins.firebase.MyFirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
    </application>
</manifest>
GeorgeRXia commented 5 years ago

@Skintillion was your suggestion for me? I tried it but still not working. i decided a work around with just using data parameter. I'm just putting this message for anyone else having this issue.

Skintillion commented 5 years ago

Yes if you don't have that it won't work. Considering we have no code of yours to really review we can but guess at it.

My background messaging works great on Android and iOS on the newest NativeScript so I can only assume its a missing configuration or something else.

GeorgeRXia commented 5 years ago

@Skintillion yeah sorry, since this project is for work I cannot share my repo. Could you guide me parts you would like to see?

felixkrautschuk commented 5 years ago

@Skintillion are you sure that one has to add the service "org.nativescript.plugins.firebase.MyFirebaseMessagingService" to the AndroidManifest? I cannot see anything in the docs of this plugin about that.

andrewbeng89 commented 5 years ago

Hi if I may add my two cents worth to the conversation, the specific issue of handling FCM data messages when the app is not running (not notification messages which are automatically handle and displayed in the system tray) is a rather important one to address, especially if any type of real-time messaging is a critical feature of your product. This becomes a big issue with third-party push notification services, like AWS Pinpoint for example, which only support data messages, therefore placing responsibility of displaying any notification based on the data payload from FCM on the app developer.

The solution which worked in my case was to implement a custom service class in JS (e.g. app/services/MyFCMService.js) which extends com.google.firebase.messaging.FirebaseMessagingService and overrides the onMessageReceived method. Here is some sample code which dispatches the data payload to LocalNotifications:

const dispatchLocalAndroidNotification = data => {
  const pinpointPrefix = "pinpoint.notification";

  const title = data[`${pinpointPrefix}.title`];
  const body = data[`${pinpointPrefix}.body`];

  if (title && body) {
    LocalNotifications.schedule([
      {
        title: data[`${pinpointPrefix}.title`],
        body: data[`${pinpointPrefix}.body`]
      }
    ]);
  }
};

com.google.firebase.messaging.FirebaseMessagingService.extend("com.tns.services.MyFCMService", {
  init: function() {},

  onMessageReceived: function(remoteMessage) {
    this.super.onMessageReceived(remoteMessage);

    const it = remoteMessage
      .getData()
      .entrySet()
      .iterator();
    let data = {};
    while (it.hasNext()) {
      const pair = it.next();
      data[pair.getKey()] = pair.getValue();
    }

    dispatchLocalAndroidNotification(data);
  }
});

A new service declaration has to be made in AndroidManifest.xml to handle the MESSAGING_EVENT:

        <service android:name="com.tns.services.MyFCMService">
             <intent-filter>
                 <action android:name="com.google.firebase.MESSAGING_EVENT"/>
             </intent-filter>
         </service>

Finally, webpack has to be configured to compile the .js code to the necessary Java class, as I am using nativescript-vue in vue.config.js:

const customServices = glob.sync(`${path.resolve(__dirname, "app/services")}/**/*.js`);

module.exports = {
  // other config...
  chainWebpack: config => {
    config.module
      .rule("native-loaders")
      .use("nativescript-dev-webpack/android-app-components-loader")
      .loader("nativescript-dev-webpack/android-app-components-loader")
      .tap(options => ({
        modules: [...options.modules, ...customServices]
      }))
      .end();
  }
}

In this case, nativescript-plugin-firebase still handles registration with registerForPushNotifications, and other important Firebase/Push features, however onMessageReceived is completely delegated to the new custom service. This appears a rather convoluted to implement a trivial feature, but to date seems like one of the few viable solutions to this problem.

wfe8006 commented 5 years ago

So my result is a little different, the onMessageReceivedCallback() seems to be able to capture the data messages even if the app is running in the background, but only for the first few minutes. If I leave my phone idle and unlock it again, the callback won't capture any new messages until I relaunch the app.

bagnos commented 5 years ago

I have the same issue, using data notification onMessageReceivedCallback() in never called when the app is in background. Any news?

Skintillion commented 5 years ago

If you have a repo I can take a look? Mine works flawlessly on Android and iOS