zo0r / react-native-push-notification

React Native Local and Remote Notifications
MIT License
6.75k stars 2.05k forks source link

Not receiving onNotification on Android #495

Closed rcarvalho closed 4 years ago

rcarvalho commented 7 years ago

iOS works for me. But, for some reason I'm not receiving the onNotification on Android at all. My code is basically the same as the sample code for Android integration. There are two scenarios I would like to outline and each have their different challenges:

1) Clicking on a push notification when app is backgrounded I never receive the onNotification call. My app comes up and I see the splash image, but my app just seems to hang there. This is strange because if I click on my backgrounded app, my app comes up fine in the last state that I left it.

2) Clicking on a push notification an app when the app is closed This launches the app fine, but I never get the onNotification call

kelvinsinsua commented 7 years ago

same problem

fredbt commented 7 years ago

same here on android.

kesha-antonov commented 7 years ago

Same +1

Please fix!

omarcarreon commented 7 years ago

I got it working following this fix https://github.com/zo0r/react-native-push-notification/issues/333#issuecomment-316811908

I had to modify it a little bit to match the bundle I was receiving. In your MainActivity.java:

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        return new ReactActivityDelegate(this, getMainComponentName()) {
            @Nullable
            @Override
            protected Bundle getLaunchOptions() {
                Intent mainIntent = getIntent();
                String dataValue = "";
                Bundle initialProps = new Bundle();
                if (mainIntent != null) {
                    Bundle bundle = mainIntent.getExtras();

                    if (bundle != null) {
                        JSONObject data = getPushData(bundle.getString("data"));
                        if (data != null) {
                            try {
                                dataValue = data.toString();
                            } catch (Exception e) {
                                // no-op
                            }
                        } else {
                        }
                    }
                }
                initialProps.putString("pushData", dataValue); // Read this inside your Root component in React native
                return initialProps;
            }
        };
    }

Modify your notification object and add a click_action key.

     },....
    notification: {
      title: "Hello, World",
      icon: "ic_launcher",
      body: "This is a notification that will be displayed if your app is in the background.",
      click_action: "OPEN_MAIN_ACTIVITY"
    }

Add an intent-filter in your Manifest inside your activity, the action name must be the same as the one you specified in your notification object.

<intent-filter>
        <action android:name="OPEN_MAIN_ACTIVITY" />
        <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

When you click your notification, it will open the activity where you added the intent-filter, read the notification data and send it through your initialProps

fredbt commented 7 years ago

@omarcarreon thanks for the detailed solution. I will try it out. A question: how and where do you initialize your PushNotification object? I use Sagas, and initialize it every time the user logs in and/or opens the app has a valid session token. In other words, it is not initialized in the main/root component of my app. Should I change this?

Thanks!

omarcarreon commented 7 years ago

No problem! I haven't used Sagas but I don't initialize PushNotification in the root component because I handle the notification after my Home component finished rendering. But if you need to handle your notification before, you can initialize it in your root component. It depends on what do you want to do with your notification

kesha-antonov commented 7 years ago

@omarcarreon Thanks! I will try it today!

kesha-antonov commented 7 years ago

@omarcarreon Can you show getPushData method?

I've got

MainActivity.java:35: error: cannot find symbol
                        JSONObject data = getPushData(bundle.getString("data"));`
                                          ^
  symbol: method getPushData(String)
1 error
:app:compileDebugJavaWithJavac FAILED
MEDLJosh commented 7 years ago

@omarcarreon +1 I am having the same issue. Could you please provide the getPushData method?

kesha-antonov commented 7 years ago

I've tried this. onNotification is not fired . Please help @omarcarreon

import com.facebook.react.ReactActivityDelegate;
import android.os.Bundle;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Intent;
import android.support.annotation.Nullable;

@Override
protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
        @Nullable
        @Override
        protected Bundle getLaunchOptions() {
            Intent mainIntent = getIntent();
            String dataValue = "";
            Bundle initialProps = new Bundle();
            if (mainIntent != null) {
                Bundle bundle = mainIntent.getExtras();

                if (bundle != null) {
                    try {
                      JSONObject data = new JSONObject(bundle.getString("data"));
                      if (data != null) {
                          try {
                              dataValue = data.toString();
                          } catch (Exception e) {
                              // no-op
                          }
                      }
                    }
                    catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
            initialProps.putString("pushData", dataValue); // Read this inside your Root component in React native
            return initialProps;
        }
    };
}
omarcarreon commented 7 years ago

@MEDLJosh @kesha-antonov sorry, it's the same method used in the RNPushNotificationListenerService.java

private JSONObject getPushData(String dataString) {
        try {
            return new JSONObject(dataString);
        } catch (Exception e) {
            return null;
        }
}
omarcarreon commented 7 years ago

@kesha-antonov onNotification will not be fired. That function only works for me to handle foreground push notifications. And to handle background and when app's killed I use this fix to send the notification data to my initialProps. In my index.android.js I can access them with this.props

MEDLJosh commented 7 years ago

@omarcarreon I was able to get the code snippet to run and not crash when receiving a payload. I am having difficulty determining if I have actually received the payload in initialProps. Do you know how to print the values of initial props in my Main.js?

omarcarreon commented 7 years ago

@MEDLJosh Yes, in your index.js you can use console.log(this.props) or enable the Remote Debugger to see them in your console.

MEDLJosh commented 7 years ago

@omarcarreon Thank you for your help! I was able to get the pushData loaded into my initial props!

kesha-antonov commented 7 years ago

@omarcarreon Thank you! I was able to get push data!

fredbt commented 7 years ago

I was able to receive notifications from my server that talks to GCM and get "onNotification" being invoked in my Android app in all states (app active, app in background, app inactive), without doing any changes in the Android-specific code -- fully relying only on the react-native-push-notification system.

My app is bootstrapped from the traditional Ignite template, and I followed some of the tips from this great post (and the author is very proactive in answering questions there!): https://shift.infinite.red/react-native-node-js-and-push-notifications-e851b279a0cd (adapted to the specific needs of my app). The key thing for me was to make sure that PushNotification.configure(...) was being called whenever the "App.js" file was executed --- this is important because of the details below :)

In order to make it work, I followed the instructions in the trouble shooting section "Silent remove push notifications" (see this https://github.com/zo0r/react-native-push-notification/blob/master/trouble-shooting.md#3-silent-remote-push-notifications) --- this part of the instructions states that:

If your Android app is not running when a silent notification is received then this library will start it. It will be started in the background however, and if the OS starts your app in this way it will not start the react-native lifecycle. This means that if your notification delivery code relies on the react-native lifecycle then it will not get invoked in this situation. You need to structure your app in such a way that PushNotification.configure gets called as a side effect of merely importing the root index.android.js file.

In my server side, I send the message to my server using the following JSON payload:

        data = {
            "to": theTokenOfTheUser
            "data": {
                "myAppMessage": jsonPayloadThatIWantToSend
            }
        }

then, the notification comes to my app with a field called "myappmessage",with my payload. Then, my app decides what to do (notifies the user with a local notification, dispatch redux store actions, etc).

As I said, when the App.js includes my Config file that has the call to configure(...), it means that configure(...) run as a side-effect of importing index.android.js.

The only issue I still have is that, when the app is inactive and gets invoked by the notification system, it runs a different lifecycle (apparently not the react lifecycle), and then the configure(...) call -- only in this particular case of the app not being active --- is called before the app is fully rendered, and this means that the Redux store and its middlewares are not necessarily ready, so if you want to call store.dispatch() it might not work well ... there are some discussions about this here: https://github.com/reactjs/redux/issues/1240 -- in my case, I'm doing a setTimeout hack as suggested, until there's a better solution to this :)

Thanks everyoe for the great discussion on this issue!

rcarvalho commented 7 years ago

@fredbt I wish I could get mine to work the way yours is working. My build.gradle in my Android project's entry file is called index.android.js. The first bit of code that I have is my Push.configure. When I click on a notification on my device and my app is closed, I see my app start up. But, I never get an onNotification callback. Attached is my code. Was wondering if you see anything wrong with it. Thanks in advance.

import ReactNative, { AppRegistry } from 'react-native'
import PushNotification from 'react-native-push-notification'
import Config from './config'
import { registerDevice } from './src/app/user/actions/user'
import './src/services/sentry'
import Root from './src/root'

console.log("EXECUTING index.android.js")

PushNotification.configure({
  onRegister: (device) => { console.log("REGISTER DEVICE", device); registerDevice(device.token, device.os) },
  onNotification: (notification) => { console.log("RECEIVED PUSH NOTIFICATION", notification) },
  senderID: Config.androidPushSenderId,
  requestPermissions: false
})

AppRegistry.registerComponent('MainComponent', () => Root)
fredbt commented 7 years ago

@rcarvalho

a few suggestions: I would move this code to its own file, let's say NotificationsConfig.js

console.log("EXECUTING index.android.js")

PushNotification.configure({
  onRegister: (device) => { console.log("REGISTER DEVICE", device); registerDevice(device.token, device.os) },
  onNotification: (notification) => { console.log("RECEIVED PUSH NOTIFICATION", notification) },
  senderID: Config.androidPushSenderId,
  requestPermissions: false
})

then include it from your ./src/root. I think it might a side-effect of running index.android.js but still should be inside the app itself (in this case, your root). Not sure about this, but my index.android.js has nothing, only the registerComponent call:

import './App/Config/ReactotronConfig'
import { AppRegistry } from 'react-native'
import App from './App/Containers/App'

AppRegistry.registerComponent('MyApp', () => App)

in the header of my App.js file (equivalent to your root file), I have:

` import '../NotificationsConfig'

`

Other than that: what's the format of the notification sent by your server to GCM (assuming that you're assuming GCM as bridge for notifications). Did you follow the instructions for silent notifications? Is silent notifications what you're trying to achieve?

Moreover, this piece of code:

onRegister: (device) => { console.log("REGISTER DEVICE", device); registerDevice(device.token, device.os) },

might not work because of what I explained about the middlewares. You might have to wrap it around a timeout call.

Hope this helps!

rcarvalho commented 7 years ago

@fredbt Are you using silent notifications? Maybe that's why. I don't want it to be silent. I want an alert that is visible to the user so that they can click on it and come back into the app. This all works in iOS, but just not in Android. And by works, I mean the following occurs, which is what I expect:

1) When my app is running I receive a onNotification without a visible display in the OS 2) When my app is backgrounded, the user receives a visible OS notification, clicks on the notification, changes the app back to an active state, and my onNotification handler is called 3) When my app is closed, the user receives a visible OS notification, clicks on it, the app is launched and I receive a call to onNofication

I really want this same behavior for Android as I get in iOS and I'm just not seeing that it is working in the same way, if at all.

rcarvalho commented 7 years ago

Sorry to beat this thread like a dead horse, but I just verified that all 3 states work on Android for silent notifications; starting from inactive, backgrounded or active. But, not for visible notifications.

fredbt commented 7 years ago

Cool @rcarvalho ... yeah the name "silent notification" is not the best one, but apparently it's exactly what you were looking for.

I guess we can assume it's working for you now?

rcarvalho commented 7 years ago

@fredbt No, I'm actually looking for visible alerts; not silent alerts. So, it still doesn't work for me. However, I found a way to do it in a kind of hacky way; by basically sending a silent notification and then receiving that and sending a local notification on the client. I was going to create a blog post about it and repost it for others.

rcarvalho commented 7 years ago

Here's my blog post in case any of you are interested: https://product.farewell.io/visible-react-native-push-notifications-that-work-on-both-ios-and-android-5e90badb4a0f

jamonholmgren commented 7 years ago

@rcarvalho Your link is borked, can you fix?

rcarvalho commented 7 years ago

@jamonholmgren Fixed

robinheinze commented 7 years ago

@rcarvalho FYI, I struggled with this issue for several days, before discovering that it was due to a quirk with our splash screen setup, which is detailed in https://github.com/zo0r/react-native-push-notification/issues/122

CptFabulouso commented 7 years ago

Thank you so much for that post @rcarvalho, the only problem I have is that I am not sure how to find out whether the user opened the app by clicking the notification or not. Did you maybe find any solution to this?

rcarvalho commented 7 years ago

Thanks, @robinheinze. I'll check it out.

@CptFabulouso, you shouldn't get an onNotification call when you just open the app. The only time it gets called is if the user clicks on the notification.

CptFabulouso commented 7 years ago

@rcarvalho According to debugger, If Iam in app and receive notification, it gets called twice, when it is received and when it is clicked. If Iam out of the app (or is killed) it gets called when it is received, but not when clicked.

rcarvalho commented 7 years ago

@CptFabuloso are you talking about Android? And, are you making sure to send a data only message from the server side? If you just do that one thing, a pure server data only push without the additional local push I explained in my blog post, you should see that you get one notification in all states when the notification is received. It is the additional local notification that will enable a callback notification when the visible banner is clicked.

CptFabulouso commented 7 years ago

@rcarvalho yes, android. I followed your blog post and have set everything as you do, also on the server side. I can see how the localNotification is called when the silentNotification incomes, but I have no idea why the callback is not invoked when the banner is clicked. If I close the app with debugger on and console log everything, this is what gets logged. Also My RootScreen is your AppContainer.

const Root = () => {
  return (
    <Provider store={store}>
      <RootScreen />
    </Provider>
  )
}
PushService INIT
PushService.js:60 PushService CONFIGURE
PushService.js:68 PushService CONFIGURE onNotification {foreground: false, subject: "Some subject", google.sent_time: 1505142177612, userInteraction: false, id: "-2143814905", …}
PushService.js:10 PushService.onNotification (inside INIT) {foreground: false, subject: "Some subject", google.sent_time: 1505142177612, userInteraction: false, id: "-2143814905", …}
-Here I click on the notification-
infoLog.js:17 Running application "..." with appParams: {"rootTag":1}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
RootScreen.js:303 render Root Screen
RootScreen.js:118 Root Screen Did Mount
PushService.js:54 PushService SET CALLBACKS
RootScreen.js:303 render Root Screen

It may be relevant, that if I reload the app at this point, I receive the original notification and also it's callback in my handleOnPushNotification (userInteraction is true in second case).

PushService INIT
PushService.js:60 PushService CONFIGURE
infoLog.js:17 Running application "..." with appParams: {"rootTag":1}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
RootScreen.js:303 render Root Screen
RootScreen.js:118 Root Screen Did Mount
PushService.js:54 PushService SET CALLBACKS
PushService.js:68 PushService CONFIGURE onNotification {foreground: true, subject: "Some subject", google.sent_time: 1505142327607, userInteraction: false, id: "-269905516", …}
RootScreen.js:83 Handle on push notification {foreground: true, subject: "Some subject", google.sent_time: 1505142327607, userInteraction: false, id: "-269905516", …}
PushService.js:68 PushService CONFIGURE onNotification {foreground: false, subject: "Some subject", google.sent_time: 1505142327607, smallIcon: "ic_notification", userInteraction: true, …}
RootScreen.js:83 Handle on push notification {foreground: false, subject: "Some subject", google.sent_time: 1505142327607, smallIcon: "ic_notification", userInteraction: true, …}
RootScreen.js:72 Handle on push register
RootScreen.js:303 render Root Screen

When you get the notification callback, does it come from the init function, or where you handle notifications in AppContainer?

Also my handleOnPushNotification

handleOnPushNotification(notification){
    console.log("Handle on push notification", notification)
    PushNotification.localNotification({
        title: notification.subject,
                message: notification.body,
                smallIcon: notification.icon ? notification.icon : "ic_notification",
                color: Colors.primaryColor,
                vibrate: true,
                vibration: 300,
               ...notification
    })
}

Does something seem fishy to you? Also thank you very much for your time looking into this

rcarvalho commented 7 years ago

@CptFabulouso the only time that the app should receive the onNotification call within init is when the application is turned off. If the app is just backgrounded setCallback has been called and you should receive the notification in AppContainer.

jurgenphotek commented 7 years ago

I'm just using local notifications and really want to be able to pass some data when the notification is clicked. I followed @omarcarreon suggestion, calling the schedule function as follows.

PushNotification.localNotificationSchedule({ message "hello" playSound false date t notification {title "test title" } data { ... } click_action "OPEN_MAIN_ACTIVITY" userInfo {tag "tag"}} })

In the MainActivity.java I'm trying various things to get any data out of the extras but it's not there. Any idea what could be wrong here?

Also, it's a bit of a pain to have to get the app into a state where it's backgrounded before I can test... is there any way of forcing an app into the background?

Help appreciated!

maartenvandillen commented 6 years ago

I followed every instruction I could find and finally got it working, onNotification was being called upon receiving a remote notification and also after opening it from background and killed app states. However, the next morning it stopped working. No code has been changed. Same device, same apk, on opening the notification onNotification is not being called when it was received in app background state.

vishalmanohar commented 6 years ago

@jurgenphotek @CptFabulouso I am having the issue too. Were you able to resolve this?

CptFabulouso commented 6 years ago

Unfortunatelly no. I switched to this library intstead

hugoh59 commented 6 years ago

Does the fact that you receive the notification on an Android device means that this library can catch it and interpret its data?

damsorian commented 6 years ago

the @omarcarreon fix works for me! thanks!

richso commented 5 years ago

As at 2019-06 for version 3.1.3

it's found that at least a channel entry in the Android App's AndroidManifest.xml is required for proper functioning of onNotification especially for non-foreground and killed-app situations and your notification type is purely Data message:

    <application ...>
    ...
       <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_name"
            android:value="default_channel"/>
        <meta-data  
   android:name="com.dieam.reactnativepushnotification.notification_channel_description"
            android:value="this is the default channel"/>
github-actions[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 30 days if no further activity occurs. Thank you for your contributions.

seongjun-kim commented 3 years ago

I had same issue and solved by another simple solution.

By modifying handler of back button in MainActivity.java, I made the back button also act like center button which makes the app go home(background).

Here is the code and link where I got idea!

more details in my blog

@Override
public void invokeDefaultOnBackPressed() {
    moveTaskToBack(true);
}