invertase / react-native-firebase

🔥 A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.71k stars 2.22k forks source link

:fire: getInitialNotification crashed in production - Android #3463

Closed jbohman closed 4 years ago

jbohman commented 4 years ago

Issue

Hello react-native-firebase team,

I observed the following crash in production:

Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Intent android.app.Activity.getIntent()' on a null object reference
       at io.invertase.firebase.messaging.ReactNativeFirebaseMessagingModule.getInitialNotification(ReactNativeFirebaseMessagingModule.java:58)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
       at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
       at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)
       at android.os.Handler.handleCallback(Handler.java:761)
       at android.os.Handler.dispatchMessage(Handler.java:98)
       at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
       at android.os.Looper.loop(Looper.java:156)
       at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
       at java.lang.Thread.run(Thread.java:776)

The log is from crashlytics, I've not been able to reproduce it locally yet. What else can I help with to debugging this issue further? I've filled in the form below with as much information I could.

Thanks


Project Files

Javascript

Click To Expand

#### `package.json`: ```json { "name": "myapp", "version": "0.1.0", "private": true, "engines": { "node": "^10.16.0", "npm": "^6.11.3" }, "devDependencies": { "@babel/core": "^7.6.2", "@babel/runtime": "^7.6.2", "@react-native-community/eslint-config": "^0.0.5", "babel-jest": "^24.9.0", "babel-plugin-transform-remove-console": "^6.9.4", "eslint": "^6.5.1", "jest": "^24.9.0", "metro-react-native-babel-preset": "0.58.0", "react-test-renderer": "16.11.0" }, "scripts": { "start": "react-native start", "android": "react-native run-android", "ios": "react-native run-ios", "test": "node node_modules/jest/bin/jest.js", "eslint": "eslint src/", "lint": "eslint ." }, "jest": { "preset": "react-native" }, "dependencies": { "@notifee/react-native": "^0.4.0", "@react-native-community/datetimepicker": "^2.3.0", "@react-native-community/netinfo": "^5.6.2", "@react-native-firebase/analytics": "^6.4.0", "@react-native-firebase/app": "^6.4.0", "@react-native-firebase/auth": "^6.4.0", "@react-native-firebase/crashlytics": "^6.4.0", "@react-native-firebase/database": "^6.4.0", "@react-native-firebase/dynamic-links": "^6.4.0", "@react-native-firebase/firestore": "^6.4.0", "@react-native-firebase/functions": "^6.4.0", "@react-native-firebase/messaging": "^6.4.0", "@react-native-firebase/perf": "^6.4.0", "@react-native-firebase/remote-config": "^6.4.0", "@react-native-firebase/storage": "^6.4.0", "moment": "^2.22.2", "moment-timezone": "^0.5.28", "prop-types": "^15.7.2", "react": "16.11.0", "react-native": "0.62.2", "react-native-camera": "^3.21.0", "react-native-dialog": "^5.6.0", "react-native-easy-markdown": "^1.5.0", "react-native-elements": "^1.2.6", "react-native-gesture-handler": "^1.4.1", "react-native-gifted-chat": "^0.13.0", "react-native-home-indicator": "^0.2.5", "react-native-image-picker": "^1.1.0", "react-native-immersive-mode": "^1.1.0", "react-native-linkedin": "^1.3.1", "react-native-modal": "^11.5.6", "react-native-modal-datetime-picker": "^8.5.1", "react-native-orientation-locker": "^1.1.8", "react-native-permissions": "2.0.0-rc.0", "react-native-static-safe-area-insets": "^2.0.0", "react-native-svg": "9.6.4", "react-native-vector-icons": "6.6.0", "react-native-video": "^5.0.2", "react-navigation": "^3.13.0", "react-navigation-hooks": "^1.1.0", "react-navigation-stack": "^1.10.3", "react-redux": "^7.1.1", "redux": "^4.0.5", "redux-thunk": "^2.3.0", "reselect": "^4.0.0", "urijs": "^1.19.2" } } ``` #### `firebase.json` for react-native-firebase v6: ```json # N/A ```


iOS

Click To Expand

#### `ios/Podfile`: - [ ] I'm not using Pods - [x] I'm using Pods and my Podfile looks like: ```ruby # N/A ``` #### `AppDelegate.m`: ```objc // N/A ```


Android

Click To Expand

#### Have you converted to AndroidX? - [x] my application is an AndroidX application? - [x] I am using `android/gradle.settings` `jetifier=true` for Android compatibility? - [ ] I am using the NPM package `jetifier` for react-native compatibility? #### `android/build.gradle`: ```groovy buildscript { ext { buildToolsVersion = "28.0.3" minSdkVersion = 21 compileSdkVersion = 28 targetSdkVersion = 28 } repositories { google() jcenter() maven { url 'https://maven.fabric.io/public' } } dependencies { classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.google.gms:google-services:4.3.3' classpath 'io.fabric.tools:gradle:1.25.4' classpath 'com.google.firebase:perf-plugin:1.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") } maven { // Android JSC is installed from npm url("$rootDir/../node_modules/jsc-android/dist") } google() jcenter() maven { url "https://www.jitpack.io" } } } ``` #### `android/app/build.gradle`: ```groovy apply plugin: "com.android.application" apply plugin: "io.fabric" apply plugin: "com.google.firebase.firebase-perf" import com.android.build.OutputFile task switchToDebug(type: Copy) { description = 'Switches to DEBUG google-services.json' from "src/debug" include "google-services.json" into "." } task switchToRelease(type: Copy) { description = 'Switches to RELEASE google-services.json' from "src/release" include "google-services.json" into "." } project.ext.react = [ entryFile: "index.js", enableHermes: true, // clean and rebuild if changing ] apply from: "../../node_modules/react-native/react.gradle" def enableSeparateBuildPerCPUArchitecture = false def enableProguardInReleaseBuilds = false def jscFlavor = 'org.webkit:android-jsc:+' def enableHermes = project.ext.react.get("enableHermes", false); android { compileSdkVersion rootProject.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.myapp" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled true versionCode 78 versionName "1.4.8" missingDimensionStrategy 'react-native-camera', 'mlkit' } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } buildTypes { release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" signingConfig signingConfigs.release } } packagingOptions { pickFirst "lib/armeabi-v7a/libc++_shared.so" pickFirst "lib/arm64-v8a/libc++_shared.so" pickFirst "lib/x86/libc++_shared.so" pickFirst "lib/x86_64/libc++_shared.so" } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode } } } } crashlytics { enableNdk true } dependencies { if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") releaseImplementation files(hermesPath + "hermes-release.aar") } else { implementation jscFlavor } implementation "androidx.appcompat:appcompat:1.0.0" implementation fileTree(dir: "libs", include: ["*.jar"]) //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { exclude group:'com.facebook.fbjni' } debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' } debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' } implementation "androidx.multidex:multidex:2.0.1" } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } afterEvaluate { processDebugGoogleServices.dependsOn switchToDebug processReleaseGoogleServices.dependsOn switchToRelease } project.ext.vectoricons = [ iconFontNames: [ 'MaterialIcons.ttf', 'MaterialCommunityIcons.ttf' ] ] apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) apply plugin: 'com.google.gms.google-services' ``` #### `android/settings.gradle`: ```groovy rootProject.name = 'myapp' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' ``` #### `MainApplication.java`: ```java import android.app.Application; import android.content.Context; import androidx.multidex.MultiDexApplication; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.soloader.SoLoader; import java.lang.reflect.InvocationTargetException; import java.util.List; public class MainApplication extends MultiDexApplication implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); return packages; } @Override protected String getJSMainModuleName() { return "index"; } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); } /** * Loads Flipper in React Native templates. Call this in the onCreate method with something like * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); * * @param context * @param reactInstanceManager */ private static void initializeFlipper( Context context, ReactInstanceManager reactInstanceManager) { if (BuildConfig.DEBUG) { try { /* We use reflection here to pick up the class that initializes Flipper, since Flipper library is not available in release mode */ Class aClass = Class.forName("com.myapp.ReactNativeFlipper"); aClass .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) .invoke(null, context, reactInstanceManager); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } ``` #### `AndroidManifest.xml`: ```xml ```


Environment

Click To Expand

**`react-native info` output:** ``` System: OS: macOS Mojave 10.14.6 CPU: (4) x64 Intel(R) Core(TM) i7-4558U CPU @ 2.80GHz Memory: 649.67 MB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 10.19.0 - /usr/local/bin/node Yarn: 1.19.0 - /usr/local/bin/yarn npm: 6.13.4 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.7.5 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1 Android SDK: API Levels: 23, 25, 26, 27, 28, 29 Build Tools: 23.0.1, 25.0.2, 26.0.2, 27.0.1, 27.0.3, 28.0.1, 28.0.2, 28.0.3, 29.0.0, 29.0.1 System Images: android-23 | Google APIs Intel x86 Atom, android-26 | Google Play Intel x86 Atom, android-27 | Google Play Intel x86 Atom, android-28 | Google APIs Intel x86 Atom, android-29 | Google Play Intel x86 Atom Android NDK: Not Found IDEs: Android Studio: 3.1 AI-173.4819257 Xcode: 11.3.1/11C505 - /usr/bin/xcodebuild Languages: Java: 1.8.0_91 - /usr/bin/javac Python: 2.7.16 - /usr/local/bin/python npmPackages: @react-native-community/cli: Not Found react: 16.11.0 => 16.11.0 react-native: 0.62.2 => 0.62.2 npmGlobalPackages: *react-native*: Not Found ``` - **Platform that you're experiencing the issue on**: - [ ] iOS - [x] Android - [ ] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - `6.4.0` - **`Firebase` module(s) you're using that has the issue:** - `Messaging` - **Are you using `TypeScript`?** - `N`

mikehardy commented 4 years ago

Huh, normally I'd say it's because you have integrated something else as your launch activity and it ate the intent (this is common with splash screen integrations) but it doesn't seem that way from AndroidManifest

You elided your package.json so that's inconclusive

Can you try using the @next tag for your firebase dependencies to get the current code which includes a mountain of messaging/notification bugfixes past 6.4.0?

jbohman commented 4 years ago

I've updated the post with the full package.json content.

I'll see if I can reproduce this in a testing environment / emulator, then I can see if version @next will solve it. Do you have any more information what changes are included?

And to add a bit more context, the place where it fails in my code is in a React.Component constructor. In that constructor I call

auth().onAuthStateChanged(async (user) => {
  // ...
  try {
    const initialNotification = await messaging().getInitialNotification();
    // Handle notification
  } catch (error) {
    // Handle error
  }
  // ...
})

But as this crash happens in the native code my try/catch doesn't help. The crash seems to be that getCurrentActivity() is null here: https://github.com/invertase/react-native-firebase/blob/master/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingModule.java#L58

mikehardy commented 4 years ago

It's a pretty big update, as this is the react-native-firebase release that will happen at the same time the Invertase Notifee library stabilizes the iOS release, and all sorts of rough edges needed smoothing as identified by Mike and Eliot while they were building Notifee - https://github.com/invertase/react-native-firebase/pull/3339

softatac commented 4 years ago

noticed this as well; tho, it's hard to test as app can't be launched in debug build from notification if killed previously

with android:launchMode="singleTop" in AndroidManifest, it doesn't crash, but also doesn't get initial notification if app was quitted

Ehesp commented 4 years ago

@softatac you can do Run -> Attach Debugger to Android Process in studio as the app is booting to debug.

@jbohman are you using any "non-standard" navigation library like the wix one?

jbohman commented 4 years ago

@Ehesp I'm using the following navigation packages:

    "react-navigation": "^3.13.0",
    "react-navigation-hooks": "^1.1.0",
    "react-navigation-stack": "^1.10.3",
softatac commented 4 years ago

@Ehesp thanks, making progress In debug build, I now get when receiving push notification, with app in DEAD state. first this:

getFolderSize() : Exception_1 = java.lang.NullPointerException: Attempt to get length of null array

later on it crashes here

    java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Intent android.app.Activity.getIntent()' on a null object reference
        at io.invertase.firebase.messaging.ReactNativeFirebaseMessagingModule.getInitialNotification(ReactNativeFirebaseMessagingModule.java:58)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:371)
        at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:150)
        at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
        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:26)
        at android.os.Looper.loop(Looper.java:224)
        at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:225)
        at java.lang.Thread.run(Thread.java:919)
2020-04-18 09:37:16.423 18087-18087/com.soferonline.soferonline E/unknown:ReactNative: Unable to launch redbox because react activity is not available, here is the error that redbox would've displayed: Attempt to invoke virtual method 'android.content.Intent android.app.Activity.getIntent()' on a null object reference

When opening notification, only app theme is loaded, but no Component gets rendered. Not even the main HOC.

Ehesp commented 4 years ago

Looks like getCurrentActivity() is returning null then, which is odd. @softatac where are you calling that method in your JS code?

softatac commented 4 years ago

@Ehesp inside a redux saga:


async function init(onRegister, onNotification, onNotificationClick) {
  try {
    if (Platform.OS === 'ios' && !isSimulator()) {
      const settings = await messaging().requestPermission();
      if (!settings) return;
    }

    // receiving
    messaging().registerDeviceForRemoteMessages().then(async () => {
      const token = await messaging().getToken();
      onRegister({ token });
    });
    messaging().onMessage(async (remoteMessage) => {
      await onNotification(remoteMessage, true);
    });
    messaging().setBackgroundMessageHandler(async (remoteMessage) => {
      await onNotification(remoteMessage, false);
    });
    messaging().onNotificationOpenedApp(async (remoteMessage) => {
      await onNotificationClick(remoteMessage, true);
    });

    // app was quitted
    try {
      // TODO, does not work WHEN: push received, app quitted then re-opened from push
      messaging().getInitialNotification().then(async (initialMessage) => {
        if (initialMessage) await onNotificationClick(initialMessage, false);
      });
    } catch (error) {
      _error('@PUSH - get initial', error);
    }
  } catch (error) {
    _error('@PUSH - generalError', error);
  }
}

function* initSaga() {
  const eventEmitter = new EventEmitter();
  const channel = yield call(notificationChannelCreator, eventEmitter);
  yield init(
    registerData => eventEmitter.emit('register', registerData),
    (notification, isForeground = false) => eventEmitter.emit('notification', notification, isForeground),
    notification => eventEmitter.emit('notificationClick', notification),
  );

  while (true) {
    const action = yield take(channel);
    yield put(action);
  }
}

export function* mainSaga() {
  yield all([
    fork(initSaga),
    // fork(testSaga),
  ]);
}

This only happens when app is killed from Task view.

mikehardy commented 4 years ago

Looks like getCurrentActivity() is returning null then, which is odd. @softatac where are you calling that method in your JS code?

I think in react-native owing to the asynchronicity of the JS/native bridge you are never guaranteed that your native code will execute while an Activity is actually present, it might have been destroyed by the time you attempt something. Last time I deep-dived on a bug related to the getCurrentActivity being unexpectedly null I came with the personal rule to never trust it / always check for null

sander48k commented 4 years ago

Looks like getCurrentActivity() is returning null then, which is odd. @softatac where are you calling that method in your JS code?

I think in react-native owing to the asynchronicity of the JS/native bridge you are never guaranteed that your native code will execute while an Activity is actually present, it might have been destroyed by the time you attempt something. Last time I deep-dived on a bug related to the getCurrentActivity being unexpectedly null I came with the personal rule to never trust it / always check for null

Do you have a solution for this issue? Currently, this library is not very useful for my project which is a bummer because there is no good alternative.

sander48k commented 4 years ago

I think I might have found a solution for this issue. The fact that the getCurrentActivity method returns null means that there is nu Activity (yet) at the moment the getInitialNotification method is called. I have no idea how the React Native startup process looks like and at what point the Activity gets instantiated but if you call the getInitialNotification method later on in the process, e.g. in the useEffect hook of the App, it all seems to work fine. The App does not crash and the RemoteMessage object even contains the correct data.

Ehesp commented 4 years ago

@softatac I don't have experience with saga, but what @sander48k said makes sense - the call may be being made before the app/activity is ready. Generally, I do all of the startup process within the useEffect. Setting up the listeners is fine since it's registering a JS event, not calling native.

jbohman commented 4 years ago

@softatac @sander48k have you managed to reproduce it in the emulator? If so, could either of you outline how you managed it? I can only see it in production crashes at the moment.

Based on your feedback @sander48k it might be solved if I move the call from React.Component constructor to componentDidMount lifecycle method. But if possible I would like to confirm it before I roll out a new version of my app.

Ehesp commented 4 years ago

If you're doing it in the constructor, that could well, is probably the issue.

softatac commented 4 years ago

@sander48k thanks for the instructions, I managed to solve it; As you mentioned, getInitialNotification gets called before ReactActivity is ready.

This happens only on Android, when notification is received and app is quitted: a HeadlessTask is started in the background. I think (haven't investigated) ReactActivity get initialised only when UI is rendered only.

@jbohman So, the solution was to call getInitialNotification only when app was fully ready. This depends on your implementation, for me with sagas it looked smth like this:

Thanks for your help

Gustavo-Kuze commented 4 years ago

Hello, I'm facing this problem too. I'm listening for messaging().getInitialNotification() from within a componentDidMount, but Firebase is throwing the same error:

image

@Ehesp can you tell us if your safe check is already available in any of these newer versions?

- 6.0.0-alpha.25
- 6.7.1
- 6.4.0-rc4
- 6.4.1-alpha.0
stale[bot] commented 4 years ago

Hello 👋, to help manage issues we automatically close stale issues. This issue has been automatically marked as stale because it has not had activity for quite some time. Has this issue been fixed, or does it still require the community's attention?

This issue will be closed in 15 days if no further activity occurs. Thank you for your contributions.

stale[bot] commented 4 years ago

Closing this issue after a prolonged period of inactivity. If this is still present in the latest release, please feel free to create a new issue with up-to-date information.