OneSignal / react-native-onesignal

React Native Library for OneSignal Push Notifications Service
Other
1.57k stars 374 forks source link

[CRASH][ANDROID] NullPointerException in getDeviceState #1159

Closed diego-paired closed 3 years ago

diego-paired commented 3 years ago

Description: We're receiving a number of crash reports originating in RNOneSignal.getDeviceState, stack trace attached below.

It affects every android SDK version and manufacturer.

Environment "react-native-onesignal": "^4.0.2", react: 17.0.1 => 17.0.1 react-native: 0.64.0-rc.2 => 0.64.0-rc.2

Steps to Reproduce Issue: Haven't been able to reproduce the issue, it's reported on Google Play's crash dashboard.

Anything else:

  at com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState (RNOneSignal.java:4)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.facebook.react.bridge.JavaMethodWrapper.invoke (JavaMethodWrapper.java:147)
  at com.facebook.react.bridge.JavaModuleWrapper.invoke (JavaModuleWrapper.java:21)
  at com.facebook.react.bridge.queue.NativeRunnable.run (NativeRunnable.java)
  at android.os.Handler.handleCallback (Handler.java:883)
  at android.os.Handler.dispatchMessage (Handler.java:100)
  at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage (MessageQueueThreadHandler.java)
  at android.os.Looper.loop (Looper.java:237)
  at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run (MessageQueueThreadImpl.java:37)
  at java.lang.Thread.run (Thread.java:919)
ild-nucleusstudio commented 3 years ago

+1.

java.lang.NullPointerException: at com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState (RNOneSignal.java:4) at java.lang.reflect.Method.invoke (Method.java) at com.facebook.react.bridge.JavaMethodWrapper.invoke (JavaMethodWrapper.java:147) at com.facebook.react.bridge.JavaModuleWrapper.invoke (JavaModuleWrapper.java:21) at com.facebook.react.bridge.queue.NativeRunnable.run (NativeRunnable.java) at android.os.Handler.handleCallback (Handler.java:873) at android.os.Handler.dispatchMessage (Handler.java:99) at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage (MessageQueueThreadHandler.java) at android.os.Looper.loop (Looper.java:224) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run (MessageQueueThreadImpl.java:37) at java.lang.Thread.run (Thread.java:764)

rgomezp commented 3 years ago

Howdy Diego, Thank you for reporting this issue.

Can you please include the code you're using? Also can you please provide environment info (e.g: device make and model, OS, etc...)?

Cheers

diego-paired commented 3 years ago

Howdy Diego, Thank you for reporting this issue.

Can you please include the code you're using? Also can you please provide environment info (e.g: device make and model, OS, etc...)?

Cheers

Thanks for your reply.

It's being reported on our Google Play console at the rate of about 50 crashes per week, I don't have any way to reproduce it.

image

renatobentorocha commented 3 years ago

Same here.

info Fetching system and libraries information... System: OS: macOS 10.15.7 CPU: (4) x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz Memory: 36.86 MB / 8.00 GB Shell: 5.7.1 - /bin/zsh Binaries: Node: 10.22.0 - ~/.nvm/versions/node/v10.22.0/bin/node Yarn: 1.22.4 - /usr/local/bin/yarn npm: 6.14.6 - ~/.nvm/versions/node/v10.22.0/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.10.0 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: iOS 13.6, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2 Android SDK: API Levels: 23, 25, 26, 28, 29 Build Tools: 28.0.3, 29.0.2, 29.0.3 Android NDK: Not Found IDEs: Android Studio: 3.6 AI-192.7142.36.36.6308749 Xcode: 11.6/11E708 - /usr/bin/xcodebuild Languages: Java: 1.8.0_242 - /usr/bin/javac Python: 2.7.16 - /usr/bin/python npmPackages: @react-native-community/cli: Not Found react: 16.13.1 => 16.13.1 react-native: 0.63.3 => 0.63.3 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

"react-native": "0.63.3", "react-native-onesignal": "^4.0.1",

Fatal Exception: java.lang.NullPointerException Attempt to invoke virtual method 'org.json.JSONObject com.onesignal.OSDeviceState.toJSONObject()' on a null object reference

com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState (RNOneSignal.java:239) java.lang.reflect.Method.invoke (Method.java) com.facebook.react.bridge.JavaMethodWrapper.invoke (JavaMethodWrapper.java:372) com.facebook.react.bridge.JavaModuleWrapper.invoke (JavaModuleWrapper.java:151) com.facebook.react.bridge.queue.NativeRunnable.run (NativeRunnable.java) android.os.Handler.handleCallback (Handler.java:883) android.os.Handler.dispatchMessage (Handler.java:100) com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage (MessageQueueThreadHandler.java:27) android.os.Looper.loop (Looper.java:241) com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run (MessageQueueThreadImpl.java:226) java.lang.Thread.run (Thread.java:919)

rgomezp commented 3 years ago

Please provide a code snippet in order to facilitate reproduction.

Cheers

renatobentorocha commented 3 years ago

@rgomezp

The error occur in OneSignal.getDeviceState() call:

const getDeviceState =  async () => {
     const deviceState = await OneSignal.getDeviceState();
     console.log({ deviceState });
}

The exception is:

Fatal Exception: java.lang.NullPointerException
Attempt to invoke virtual method 'org.json.JSONObject com.onesignal.OSDeviceState.toJSONObject()' on a null object reference

com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState (RNOneSignal.java:239)
java.lang.reflect.Method.invoke (Method.java)
com.facebook.react.bridge.JavaMethodWrapper.invoke (JavaMethodWrapper.java:372)
com.facebook.react.bridge.JavaModuleWrapper.invoke (JavaModuleWrapper.java:151)
com.facebook.react.bridge.queue.NativeRunnable.run (NativeRunnable.java)
android.os.Handler.handleCallback (Handler.java:883)
android.os.Handler.dispatchMessage (Handler.java:100)
com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage (MessageQueueThreadHandler.java:27)
android.os.Looper.loop (Looper.java:241)
com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run (MessageQueueThreadImpl.java:226)
java.lang.Thread.run (Thread.java:919)

The OneSignal and React Native packages are:

"react-native": "0.63.3",
"react-native-onesignal": "^4.0.1",

The react native info:

info Fetching system and libraries information...
System:
OS: macOS 10.15.7
CPU: (4) x64 Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
Memory: 36.86 MB / 8.00 GB
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 10.22.0 - ~/.nvm/versions/node/v10.22.0/bin/node
Yarn: 1.22.4 - /usr/local/bin/yarn
npm: 6.14.6 - ~/.nvm/versions/node/v10.22.0/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.10.0 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 13.6, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
Android SDK:
API Levels: 23, 25, 26, 28, 29
Build Tools: 28.0.3, 29.0.2, 29.0.3
Android NDK: Not Found
IDEs:
Android Studio: 3.6 AI-192.7142.36.36.6308749
Xcode: 11.6/11E708 - /usr/bin/xcodebuild
Languages:
Java: 1.8.0_242 - /usr/bin/javac
Python: 2.7.16 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: 16.13.1 => 16.13.1
react-native: 0.63.3 => 0.63.3
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found
dijinshopwise commented 3 years ago

Getting same issue.

"react-native": "^0.63.4", "react-native-onesignal": "^4.0.3"

I upgraded from "react-native-onesignal": "^3.9.3" to "react-native-onesignal": "^4.0.3". I am getting crashes now. I have updated the init code same as above. I added freshly.

... let device = await OneSignal.getDeviceState(); ...

From Play Store Crash Log

java.lang.NullPointerException: at com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState (RNOneSignal.java) at java.lang.reflect.Method.invoke (Method.java) at com.facebook.react.bridge.JavaMethodWrapper.invoke (JavaMethodWrapper.java) at com.facebook.react.bridge.JavaModuleWrapper.invoke (JavaModuleWrapper.java) at com.facebook.react.bridge.queue.NativeRunnable.run (NativeRunnable.java) at android.os.Handler.handleCallback (Handler.java:739) at android.os.Handler.dispatchMessage (Handler.java:95) at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage (MessageQueueThreadHandler.java) at android.os.Looper.loop (Looper.java:148) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run (MessageQueueThreadImpl.java) at java.lang.Thread.run (Thread.java:818)

Firebase Crash log

Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'org.json.JSONObject com.onesignal.g0.a()' on a null object reference at com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState(RNOneSignal.java:4) at java.lang.reflect.Method.invoke(Method.java)

rgomezp commented 3 years ago

Howdy, Thank you for the information, but if someone could provide a code example that would be most helpful. This

const getDeviceState = async () => { const deviceState = await OneSignal.getDeviceState(); console.log({ deviceState }); }

doesn't quite provide enough information to reproduce. What we need is some more context as to how and where this is being used.

E.g: how does the call to getDeviceState relate to the OneSignal initialization? or what RN lifecycle function is this being called in?

Please make sure to provide all the context necessary to resolve this since the stack traces provided don't quite provide what we need.

I suspect this might be happening if getDeviceState is called too quickly. Can you try moving getDeviceState to a point in your code that is guaranteed to run after OneSignal initialization completes? e.g: put it in the callback to the subscription change observer.

Remember that getDeviceState captures a snapshot at the time it is called. For that reason do not cache the state, but rather re-get it as needed.

Reference

renatobentorocha commented 3 years ago

@rgomezp

Hi, in the App.tsx, our entry point, we have:

const oneSignalIntialize = async () => {
  OneSignal.setAppId('xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx');
}

const App = () => {
  useEffect(() => {
    oneSignalIntialize();

  }, []);

return (
    <LoginFlow />
);

}

After the login flow, if this was succeed, we have a welcome screen:

const getDeviceState = async () => {
    const deviceState = await OneSignal.getDeviceState(); 
    console.log({ deviceState });
}

const WelcomeScreen = () => {
  useEffect(() => {
    getDeviceState();

  }, []);

return (
    <Components />
);

}
Screen Shot 2021-02-18 at 10 59 35
tyang1 commented 3 years ago

Hi @renatobentorocha , Thank you for sharing the code with us! The useEffect hook here may be called to initialize the SDK within the main App component. This is essentially an async call that gets called as soon as the component is mounted, but there's no state that checks if the initialization has been completed.  You can use the useState hook to add a state to check for the init status before rendering the Welcome screen.  Another recommendation, as suggested by @rgomezp , would be to run it in the callback to the addSubscriptionObserver (sorry for me speaking too quickly on the deprecated getPermissionSubscriptionState earlier), which would guarantee that the SDK has been successfully initialized before accessing the device state.  Let us know if this helps resolving the issue,  Thanks 

renatobentorocha commented 3 years ago

Hi @tyang1 Thanks by attention, but the one-signal docs for that callback (getPermissionSubscriptionState) say:

Screen Shot 2021-02-22 at 09 07 47

I will test with addSubscriptionObserver and will let you know what happen

oferRounds commented 3 years ago

Getting this also. Don’t know how to reproduce. @rgomezp perhaps it’s a threading issue? This happens randomly, but quite a lot. We see many traces for in our crash report

oferRounds commented 3 years ago

image

rgomezp commented 3 years ago

Howdy, I don't think this is a threading issue as the stack traces point to it more likely occurring at the wrapper level. This could certainly be a bug. We just need reliable reproduction steps to move forward with a resolution.

It is interesting @renatobentorocha that you report it is happening 98% in background.

@oferRounds @dijinshopwise is this something you are also seeing?

dan-developer commented 3 years ago

+1

dan-developer commented 3 years ago

image

dan-developer commented 3 years ago

@rgomezp follow the code:

async componentDidMount() {
        this._isMounted = true

        /* O N E S I G N A L   S E T U P */
        OneSignal.setAppId('***********************************')
        OneSignal.setLogLevel(0, 0)
        OneSignal.setRequiresUserPrivacyConsent(false)

        /* O N E S I G N A L  H A N D L E R S */
        OneSignal.setNotificationWillShowInForegroundHandler(notifReceivedEvent => {
            let notification = notifReceivedEvent.getNotification()

            const buttonOk = {
                text: 'OK', onPress: () => {
                    notifReceivedEvent.complete()
                },
            }

            Alert.alert(notification.title, notification.body, [buttonOk])

            if (notification.additionalData) {
                //
            }

        })
        OneSignal.setNotificationOpenedHandler(notification => {
            //
        })

        OneSignal.disablePush(true)

        const deviceState = await OneSignal.getDeviceState()

        if (Platform.OS === 'ios' && !deviceState.isSubscribed) {
            OneSignal.promptForPushNotificationsWithUserResponse(function(accepted) {
                //
            });
        }

        // ...
}
dan-developer commented 3 years ago

I downgrade to old version and Google Play warns about same error.

com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState
dan-developer commented 3 years ago

Any suggestion? Any temporary solution?

diego-paired commented 3 years ago

We're still receiving hundreds of these crashes, any news? image

oferRounds commented 3 years ago

@diego-paired our temporary workaround was to delay the call to getDeviceState() after OneSingal init (we added 2 seconds delay)

diego-paired commented 3 years ago

@diego-paired our temporary workaround was to delay the call to getDeviceState() after OneSingal init (we added 2 seconds delay)

thanks for the tip

renatobentorocha commented 3 years ago

Hi @renatobentorocha , Thank you for sharing the code with us! The useEffect hook here may be called to initialize the SDK within the main App component. This is essentially an async call that gets called as soon as the component is mounted, but there's no state that checks if the initialization has been completed.  You can use the useState hook to add a state to check for the init status before rendering the Welcome screen.  Another recommendation, as suggested by @rgomezp , would be to run it in the callback to the addSubscriptionObserver (sorry for me speaking too quickly on the deprecated getPermissionSubscriptionState earlier), which would guarantee that the SDK has been successfully initialized before accessing the device state.  Let us know if this helps resolving the issue,  Thanks

@diego-paired @dan-developer

I followed the @rgomezp suggestion and the crashes, for now, stopped occur.

OneSignal.addSubscriptionObserver(() => {
    OneSignal.getDeviceState().then((deviceState) => {
      console.log({deviceState})
    });
  });
jfishman1 commented 3 years ago

Using the test app, added this code to a button:

Situation 1

let getDeviceStateButton = this.renderButtonView(
"Get Device State",
isExternalUserIdLoading || isPrivacyConsentLoading,
() => {

console.log("Attempting to get device state");
this.setState({ isExternalUserIdLoading: true }, () => {
// OneSignal getDeviceState
let deviceState = OneSignal.getDeviceState();
console.log("deviceState: ", deviceState);
console.log("deviceState.isSubscribed: ", deviceState.isSubscribed);
this.setState({ isExternalUserIdLoading: false, isSubscribed: deviceState.isSubscribed });
console.log("this.state.isSubscribed after calling setState: ", this.state.isSubscribed);
console.log("deviceState.userId: ", deviceState.userId)
});
});

Log shows:

2021-03-25 20:06:14.589544-0700 jonexample[751:27269] [javascript] Attempting to get device state
2021-03-25 20:06:14.645290-0700 jonexample[751:27269] [javascript] 'deviceState: ', { _U: 0, _V: 0, _W: null, _X: null }
2021-03-25 20:06:14.647385-0700 jonexample[751:27269] [javascript] 'deviceState.isSubscribed: ', undefined
2021-03-25 20:06:14.647768-0700 jonexample[751:27269] [javascript] 'this.state.isSubscribed after calling setState: ', undefined
2021-03-25 20:06:14.647897-0700 jonexample[751:27269] [javascript] 'deviceState.userId: ', undefined

However when using the example code provided in the componentDidMount:

Situation 2

async componentDidMount(){
 const deviceState = await OneSignal.getDeviceState();
this.setState({ isSubscribed : deviceState.isSubscribed});
console.log("componentDidMount deviceState: ", deviceState);
console.log("componentDidMount deviceState.isSubscribed: ", deviceState.isSubscribed);
console.log("componentDidMount deviceState.userId: ", deviceState.userId);
}

The log shows:

2021-03-25 20:14:46.521099-0700 jonexample[1326:55465] [javascript] 'componentDidMount deviceState: ', { userId: '92aa2978-3f53-454e-b1a6-652c43f0dba4',
2021-03-25 20:14:46.521328-0700 jonexample[1326:55465] [javascript] 'componentDidMount deviceState.isSubscribed: ', true
2021-03-25 20:14:46.521435-0700 jonexample[1326:55465] [javascript] 'componentDidMount deviceState.userId: ', '92aa2978-3f53-454e-b1a6-652c43f0dba4'

@rgomezp is scenario 1 expected behavior?

@diego-paired @renatobentorocha @dan-developer could y'all share your examples or confirm if both above situations is what you see?

Please be detailed in how you are using this method and include steps for us to reproduce.

rgomezp commented 3 years ago

@jfishman1 Take a look at the documentation. You will see that getDeviceState is an asynchronous function so make sure to await on it, or to use a then.

Regardless, it appears there's a good opportunity to handle this in the SDK to avoid these crashes. Thanks for surfacing. We will explore what changes can be made to more seamlessly handle these.

rgomezp commented 3 years ago

Digging into this a bit further, it appears that the most likely cause of this is the native side is trying to get the device state before the context has loaded.

if (appContext == null) {
         logger.error("OneSignal.initWithContext has not been called. Could not get OSDeviceState");
         return null;
      }

source

To confirm, please look for OneSignal.initWithContext has not been called. Could not get OSDeviceState in your native logcat (e.g: open Android Studio).

In the meantime, it seems we can add a check in the Android bridge to prevent a full on crash.

Regardless, please note: We still recommend only getting the device state via the subscription change observer to ensure you're not getting the device state too early

Thanks for your patience y'all.


From #1085: Instead of delaying the device state getter via a timer, try putting the getDeviceState function call in the subscription observer. This way, you will know that it is subscribed. e.g:

OneSignal.addSubscriptionObserver(async (event) => {
    this.OSLog("OneSignal: subscription changed:", event);
    if (event.to.isSubscribed) {
        const state = await OneSignal.getDeviceState();
        // do something with the device state
    }
});
rgomezp commented 3 years ago

Update: this is now fixed in the latest version 4.0.7

EnginYilmaz commented 3 years ago

I updated to the latest 4.0.7 repo. I am not sure if I am asking on the right place but on some iOS simulators and Android devices I even only get these fields with deviceState for example like this one. There is even no userId field.

{"rootTag":21,"initialProps":{}}
 WARN  {"hasNotificationPermission": false, "isEmailSubscribed": false, "isPushDisabled": false, "isSMSSubscribed": false, "isSubscribed": false, "notificationPermissionStatus": 0}
bhaktitud commented 3 years ago

Digging into this a bit further, it appears that the most likely cause of this is the native side is trying to get the device state before the context has loaded.

if (appContext == null) {
         logger.error("OneSignal.initWithContext has not been called. Could not get OSDeviceState");
         return null;
      }

source

To confirm, please look for OneSignal.initWithContext has not been called. Could not get OSDeviceState in your native logcat (e.g: open Android Studio).

In the meantime, it seems we can add a check in the Android bridge to prevent a full on crash.

Regardless, please note: We still recommend only getting the device state via the subscription change observer to ensure you're not getting the device state too early

Thanks for your patience y'all.

From #1085: Instead of delaying the device state getter via a timer, try putting the getDeviceState function call in the subscription observer. This way, you will know that it is subscribed. e.g:

OneSignal.addSubscriptionObserver(async (event) => {
    this.OSLog("OneSignal: subscription changed:", event);
    if (event.to.isSubscribed) {
        const state = await OneSignal.getDeviceState();
        // do something with the device state
    }
});

I've tried that but it is not 100% work, like sometimes i got the device id sometimes not, mostly the negative case happens when on the first launch of the app. So i have to close the app first, then the device id will be fetch successfully. any idea?

EnginYilmaz commented 2 years ago

Is there any body still getting this error in 28 July 2022 or only me?

anwersolangi commented 2 years ago

Is there any body still getting this error in 28 July 2022 or only me?

Getting the same error. However not often or rarely on some of devices like Redmi and Infinix.

MursiDirect commented 1 year ago

Same issue here, any solution yet?

iglebekk commented 6 months ago

One year later; Same issue here, any solution yet?