expo: `NullPointerException`s when calling `Updates.reloadAsync` with SDK49 in production builds

EDIT: Please see linked comment for repro steps. The cause seems to be from calling reloadAsync from expo-updates.

Minimal reproducible example

https://github.com/patidarsarvesh/ExpoNullPointerIssue

Summary

The problem

We are noticing our app crashing on android with NullPointerException occasionally in production. It is affecting <1% of users, but it’s still consistently happening 1-2 times a day.

Judging from our logs, all crashes seem to be caused by NullPointerException with expo packages. We have seen them so far occur in expo-notifications and expo-location. Here are the stack traces we have:

expo-location

java.lang.NullPointerException: java.lang.NullPointerException
    at expo.modules.kotlin.jni.JavaCallback.invoke(JavaCallback.kt)
    at expo.modules.kotlin.jni.JavaCallback.invoke(JavaCallback.kt:24)
    at expo.modules.kotlin.PromiseKt$toBridgePromise$resolveMethod$1.invoke(Promise.kt:22)
    at expo.modules.kotlin.PromiseKt$toBridgePromise$resolveMethod$1.invoke(Promise.kt:22)
    at expo.modules.kotlin.PromiseKt$toBridgePromise$1.resolve(Promise.kt:29)
    at expo.modules.adapters.react.PromiseWrapper.resolve(PromiseWrapper.java:27)
    at expo.modules.location.LocationHelpers$1.onLocationChanged(LocationHelpers.java:157)
    at expo.modules.location.LocationModule$1.onLocationResult(LocationModule.java:557)
    at com.google.android.gms.internal.location.zzaw.notifyListener(com.google.android.gms:play-services-location@@20.0.0:2)
    at com.google.android.gms.common.api.internal.ListenerHolder.zaa(com.google.android.gms:play-services-base@@18.1.0:2)
    at com.google.android.gms.common.api.internal.zacb.run
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)

expo-notifications

java.lang.NullPointerException: java.lang.NullPointerException
    at expo.modules.kotlin.jni.JavaCallback.invoke(JavaCallback.kt)
    at expo.modules.kotlin.jni.JavaCallback.invoke(JavaCallback.kt:24)
    at expo.modules.kotlin.jni.PromiseImpl.reject(PromiseImpl.kt:87)
    at expo.modules.kotlin.PromiseKt$toBridgePromise$1.reject(Promise.kt:41)
    at expo.modules.adapters.react.PromiseWrapper.reject(PromiseWrapper.java:36)
    at expo.modules.notifications.notifications.presentation.ExpoNotificationPresentationModule$4.onReceiveResult(ExpoNotificationPresentationModule.java:98)
    at android.os.ResultReceiver$MyResultReceiver.send(ResultReceiver.java:59)
    at android.os.ResultReceiver.send(ResultReceiver.java:93)
    at expo.modules.notifications.service.NotificationsService.handleIntent(NotificationsService.kt:650)
    at expo.modules.notifications.service.NotificationsService$onReceive$1.invoke(NotificationsService.kt:588)
    at expo.modules.notifications.service.NotificationsService$onReceive$1.invoke(NotificationsService.kt:586)
    at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
java.lang.NullPointerException: java.lang.NullPointerException
    at expo.modules.kotlin.jni.JavaCallback.invoke(JavaCallback.kt)
    at expo.modules.kotlin.jni.JavaCallback.invoke(JavaCallback.kt:24)
    at expo.modules.kotlin.jni.PromiseImpl.reject(PromiseImpl.kt:87)
    at expo.modules.kotlin.PromiseKt$toBridgePromise$1.reject(Promise.kt:41)
    at expo.modules.adapters.react.PromiseWrapper.reject(PromiseWrapper.java:36)
    at expo.modules.notifications.tokens.PushTokenModule$1.onComplete(PushTokenModule.java:72)
    at com.google.android.gms.tasks.zzi.run(com.google.android.gms:play-services-tasks@@18.0.2:1)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8855)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)

other details

  • In production, sentry breadcrumbs suggest the expo-notifications crashes happen on app launch (when we setup the notification handler) and when we fetch the expo push token.
  • Logs also suggest that the expo-locations crash happens while we fetch location data from the device
  • Crashes so far seen on google and samsung devices, and across many different OS versions (10, 11, 13)
  • These crashes somewhat resemble #23382 and #17426, but nothing really matches up with what we’re seeing here

repro

We are having problems consistently reproducing the problem, but here is a repo with what we think are the minimum requirements to trigger the crash.

We have tried everything we can think of to try to reproduce it consistently, such as accepting/rejecting permissions, fetching location data frequently, triggering the push notification handler, fetching the push token frequently, and testing from cold start and on entering foreground. see https://github.com/expo/expo/issues/24159#issuecomment-1701761218

If you have any tips on debugging native crashes like this we would be happy to apply them and followup. Both in the reproduction repo and in our production app we don’t write any native code ourselves. We have thought about downgrading to expo 48 to see if that fixes the problem but we didn’t want to go through all that effort yet without knowing the root cause.

Environment

  • We use eas-build to build our app for ios & android, but the following is one of our local dev machines
expo-env-info 1.0.5 environment info:
    System:
      OS: macOS 12.6
      Shell: 5.8.1 - /bin/zsh
    Binaries:
      Node: 16.19.0 - ~/.asdf/installs/nodejs/16.19.0/bin/node
      Yarn: 1.22.19 - ~/.asdf/installs/nodejs/16.19.0/bin/yarn
      npm: 8.19.3 - ~/git_repos/<project_name>/node_modules/.bin/npm
    Managers:
      CocoaPods: 1.12.0 - /opt/homebrew/bin/pod
    SDKs:
      iOS SDK:
        Platforms: DriverKit 22.1, iOS 16.1, macOS 13.0, tvOS 16.1, watchOS 9.1
    IDEs:
      Android Studio: 2021.3 AI-213.7172.25.2113.9014738
      Xcode: 14.1/14B47b - /usr/bin/xcodebuild
    npmPackages:
      @expo/metro-config: ^0.10.7 => 0.10.7
      expo: ^49.0.6 => 49.0.6
      react: 18.2.0 => 18.2.0
      react-dom: ^18.2.0 => 18.2.0
      react-native: 0.72.3 => 0.72.3
      react-native-web: ~0.19.6 => 0.19.7
    npmGlobalPackages:
      eas-cli: 4.1.2
      expo-cli: 6.3.10
    Expo Workflow: managed

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Reactions: 2
  • Comments: 21 (11 by maintainers)

Commits related to this issue

Most upvoted comments

@jludwiggreenaction I am experiencing this issue in production and the crash rate has significantly risen. I believe calling Updates.reloadAsync is the cause, The App crashes whenever we push out an update. How have you been able to manage this issue so far?

We don’t have a solution yet. Most we could do is try to call refreshAsync less often.

@lukmccall I apologize for the ping on a closed issue, but do you know if there’s a chance this fix can be backported to sdk 49?

I want to reiterate that although we only see logs of this occurring on android with NullPointExceptions in sentry, I was able to crash IOS as with the same steps. It just seems that sentry is unable to log anything for IOS in that case.

@rafavalerio The fix is included in SDK 50, but after requesting it for months I don’t see this fix ever being backported to SDK 49 unfortunately.

Is it possible to include 037566e with the v49 sdk? It’s been merged to main for a month but we’re still dealing with this on v49.

I’m facing the same issue on Android at first app lunch…

image image

I am experiencing the same issue. Here’s some of the stack traces:

Exception java.lang.NullPointerException: java.lang.NullPointerException
  at expo.modules.kotlin.jni.JavaCallback.invoke
  at expo.modules.kotlin.jni.JavaCallback.invoke (JavaCallback.kt:24)
  at expo.modules.kotlin.jni.PromiseImpl.reject (PromiseImpl.kt:87)
  at expo.modules.kotlin.Promise$DefaultImpls.reject (Promise.kt:15)
  at expo.modules.kotlin.jni.PromiseImpl.reject (PromiseImpl.kt:26)
  at expo.modules.kotlin.functions.AsyncFunction$attachToJSObject$2$functionBody$1.invoke (AsyncFunction.kt:74)
  at expo.modules.kotlin.functions.AsyncFunction$attachToJSObject$2$functionBody$1.invoke (AsyncFunction.kt:66)
  at expo.modules.kotlin.functions.AsyncFunction$attachToJSObject$2$3.invokeSuspend (AsyncFunction.kt:104)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
  at android.os.Handler.handleCallback (Handler.java:942)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.os.HandlerThread.run (HandlerThread.java:67)

Expo updates:

Exception java.lang.NullPointerException: java.lang.NullPointerException
  at expo.modules.kotlin.jni.JavaCallback.invoke
  at expo.modules.kotlin.jni.JavaCallback.invoke (JavaCallback.kt:24)
  at expo.modules.kotlin.PromiseKt$toBridgePromise$resolveMethod$1.invoke (Promise.kt:22)
  at expo.modules.kotlin.PromiseKt$toBridgePromise$resolveMethod$1.invoke (Promise.kt:22)
  at expo.modules.kotlin.PromiseKt$toBridgePromise$1.resolve (Promise.kt:29)
  at expo.modules.adapters.react.PromiseWrapper.resolve (PromiseWrapper.java:27)
  at expo.modules.updates.UpdatesModule$checkForUpdateAsync$1$1.onSuccess (UpdatesModule.kt:188)
  at expo.modules.updates.loader.FileDownloader.parseRemoteUpdateResponse$expo_updates_release (FileDownloader.kt:116)
  at expo.modules.updates.loader.FileDownloader$downloadRemoteUpdate$1.onResponse (FileDownloader.kt:548)
  at expo.modules.updates.loader.FileDownloader$downloadData$1.onResponse (FileDownloader.kt:622)
  at okhttp3.internal.connection.RealCall$AsyncCall.run (RealCall.kt:519)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1137)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:637)
  at java.lang.Thread.run (Thread.java:1012)

Expo Location

Exception java.lang.NullPointerException: java.lang.NullPointerException
  at expo.modules.kotlin.jni.JavaCallback.invoke
  at expo.modules.kotlin.jni.JavaCallback.invoke (JavaCallback.kt:24)
  at expo.modules.kotlin.PromiseKt$toBridgePromise$resolveMethod$1.invoke (Promise.kt:22)
  at expo.modules.kotlin.PromiseKt$toBridgePromise$resolveMethod$1.invoke (Promise.kt:22)
  at expo.modules.kotlin.PromiseKt$toBridgePromise$1.resolve (Promise.kt:29)
  at expo.modules.adapters.react.PromiseWrapper.resolve (PromiseWrapper.java:27)
  at expo.modules.location.LocationHelpers$1.onLocationChanged (LocationHelpers.java:157)
  at expo.modules.location.LocationModule$1.onLocationResult (LocationModule.java:557)
  at com.google.android.gms.internal.location.zzaw.notifyListener (com.google.android.gms:play-services-location@@20.0.0:2)
  at com.google.android.gms.common.api.internal.ListenerHolder.zaa (com.google.android.gms:play-services-base@@18.1.0:2)
  at com.google.android.gms.common.api.internal.zacb.run
  at android.os.Handler.handleCallback (Handler.java:942)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:226)
  at android.os.Looper.loop (Looper.java:313)
  at android.os.HandlerThread.run (HandlerThread.java:67)