React-native-onesignal: [CRASH][ANDROID] NullPointerException in getDeviceState

Created on 6 Feb 2021  ·  28Comments  ·  Source: OneSignal/react-native-onesignal

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)
Android Possible Bug

Most helpful comment

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

thanks for the tip

All 28 comments

+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)

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

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

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)

Please provide a code snippet in order to facilitate reproduction.

Cheers

@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:

```typescript
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

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)

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

@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

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


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

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

image

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?

+1

image

@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) {
                //
            });
        }

        // ...
}

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

com.geektime.rnonesignalandroid.RNOneSignal.getDeviceState

Any suggestion? Any temporary solution?

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

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

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

thanks for the tip

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})
    });
  });

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.

@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.

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
    }
});

Update: this is now fixed in the latest version 4.0.7

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}
Was this page helpful?
0 / 5 - 0 ratings