react-native-firebase icon indicating copy to clipboard operation
react-native-firebase copied to clipboard

[🐛] Android: ReactNative root component is getting unmounted when open PushNotification when app is in Foreground

Open olegmilan opened this issue 3 years ago • 4 comments
trafficstars

Issue

We experience an issue on Android, when open PushNotification from notification tray, when is in the Foreground. App root component is getting unmounted and never gets mounted again until you restart the app. NOTE: everything is working as expected if you open Notification when the App is in the Background or App is Suspended. This is a quite old issue and it's happening on older version of RN Firebase messaging. Why I'm sure this is related to RN Firebase messaging? We are forced to use a second library to handle other notifications: react-native-push-notification. And this issue is not happening for Notifications handled by that library. I also tried to remove react-native-push-notification from the project to make sure, second library doesn't affect here. It didn't help. App entry file code is attached to Javascript Project Files section

What happens when open PushNotification from tray when app is in the Foreground:

  1. App componentDidMount is called
  2. App componentWillUnmount is called. And that's it. App is never gets mounted until restarted

What happens when open PushNotification from tray when app is in the Background:

  1. App componentWillUnmount is called
  2. App componentDidMount is called. And everything is working

Project Files

Javascript

Click To Expand

package.json:

    "react-native": "0.67.4",
    "@react-native-firebase/analytics": "^15.3.0",
    "@react-native-firebase/app": "^15.3.0",
    "@react-native-firebase/auth": "^15.3.0",
    "@react-native-firebase/crashlytics": "^15.3.0",
    "@react-native-firebase/database": "^15.3.0",
    "@react-native-firebase/messaging": "^15.3.0",

firebase.json for react-native-firebase v6:

"react-native": {
    "messaging_android_notification_channel_id": "high-priority"
  }

App.js App entry file:

import React from 'react';
import 'react-native-gesture-handler';
import { AppRegistry } from 'react-native';

import RootContainer from './RootContainer';

// // eslint-disable-next-line
// function HeadlessCheck({ isHeadless }) {
//   console.log('DEBUG: HeadlessCheck', isHeadless);
//   if (isHeadless) {
//     // App has been launched in the background by iOS, ignore
//     return null;
//   }
//
//   return <RootContainer />;
// }

class App extends React.Component {
  componentDidMount() {
    console.log('DEBUG: componentDidMount');
  }
  componentWillUnmount() {
    console.log('DEBUG: componentWillUnmount');
  }

  render() {
      return <RootContainer />;
  }
}

function registerApp() {
  AppRegistry.registerComponent('AppName', () => App);
}
export default registerApp;

iOS

Click To Expand

ios/Podfile:

  • [ ] I'm not using Pods
  • [x] I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// 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?
  • [x] I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.awesome.AwesomeAppMobile"
          android:versionCode="1"
          android:versionName="1.0">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <!--IncomingCall Permissions Start-->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- Used by react-native-foreground-service too -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <permission
        android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
        android:protectionLevel="signature" />
    <!--IncomingCall Permissions End-->

    <application
      android:name=".MainApplication"
      android:allowBackup="true"
      tools:replace="android:label"
      android:label="AwesomeApp"
      android:launchMode="singleTop"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:theme="@style/AppTheme"
      android:largeHeap="true"
      android:requestLegacyExternalStorage="true"
      android:extractNativeLibs="true"
      android:supportsRtl="true">
      <activity
        android:name=".MainActivity"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustNothing"
        android:theme="@style/AppTheme.Launcher"
        android:showWhenLocked="true"
        android:turnScreenOn="true"
        android:showOnLockScreen="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
        </intent-filter>
      </activity>
        <activity
            android:name=".LockScreenActivity"
            android:showWhenLocked="true"
            android:turnScreenOn="true"
            android:showOnLockScreen="true">
        </activity>
        <activity android:name="com.microsoft.identity.client.BrowserTabActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="msal45816fca-1b4c-4132-ac81-0db4e0f8834d" android:host="auth" />
            <data android:scheme="msal8b3ac36e-2524-48fc-8182-9967ea3d5ac3" android:host="auth" />
        </intent-filter>
      </activity>
      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
      <service android:name="com.kontakt.sdk.android.ble.service.ProximityService" android:exported="false"/>

      <!--firebase config-->
      <meta-data
          android:name="com.google.firebase.messaging.default_notification_icon"
          android:resource="@mipmap/ic_notification"
          tools:replace="android:resource"
      />
      <meta-data android:name="com.google.firebase.messaging.default_notification_color"
                 android:resource="@color/notification_icon"
                 tools:replace="android:resource"
      />
</application>
    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" />
        </intent>
    </queries>
</manifest>


Environment

Click To Expand

react-native info output:

System:
    OS: macOS 12.5.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 264.78 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.14.2 - /usr/local/bin/node
    Yarn: Not Found
    npm: 8.5.0 - /usr/local/bin/npm
    Watchman: 2022.07.04.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: 1.11.3 - /opt/homebrew/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5
    Android SDK: Not Found
  IDEs:
    Android Studio: 2021.2 AI-212.5712.43.2112.8609683
    Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.16 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.2 => 17.0.2 
    react-native: 0.67.4 => 0.67.4 
    react-native-macos: Not Found
  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:
    • 15.3.0
  • Firebase module(s) you're using that has the issue:
    • @react-native-firebase/messaging
  • Are you using TypeScript?
    • N

olegmilan avatar Sep 12 '22 16:09 olegmilan

That's odd - I don't recall encountering anything like that

Can you post the JSON you can send to the FCM REST APIs that triggers the notification on the device that you interact with?

Can you reduce this to just an index.js / App.js pair, where you have registered the firebase listeners for notifications onMessage/onNotificationOpenedApp/setBackgroundMessageHandler ?

Also might be interesting to register a react-native AppState listener to see what change events are fired there.

No one else has reported this and it's pretty severe - if the root component unmounts then all the other components would unmount, correct? I feel like I would have seen that in my work apps, so this is most unexpected

mikehardy avatar Sep 12 '22 22:09 mikehardy

thanks for response @mikehardy

I combined index.android.js + App.js + firebaseService where we attach listeners:

import React from 'react';
import { AppRegistry, AppState } from 'react-native';

import messaging from '@react-native-firebase/messaging';

import RootContainer from './RootContainer';

AppState.addEventListener('change', (nextAppState) => {
  console.log('DEBUG: handleAppStateChange', nextAppState);
});

class HeadlessCheck extends React.Component {
  componentDidMount() {
    console.log('DEBUG: componentDidMount');

    this.initFirebase();
  }
  componentWillUnmount() {
    console.log('DEBUG: componentWillUnmount');

    this.removeListeners();
  }

  removeListeners() {
    if (this.removeMessageListener) {
      this.removeMessageListener();
    }

    if (this.removeNotificationOpenedAppListener) {
      this.removeNotificationOpenedAppListener();
    }
  }

  initFirebase() {
    messaging().getToken().then((token) => {
      // TODO: perform some actions with token

      // On message callback is being called when app receives notification with data inside
      // but without body. That means that message can only be shown when app is in foreground
      this.removeMessageListener = messaging().onMessage((message) => {
        console.log('DEBUG: onMessage', message);

        // TODO: format message and perform some action
      });

      // `onNotificationOpenedApp` callback is triggered when app was opened via notification from Background state (not Suspended)
      this.removeNotificationOpenedAppListener = messaging().onNotificationOpenedApp((message) => {
        console.log('DEBUG: onNotificationOpenedApp', message);

        // TODO: format message and perform some action
      });

      // `getInitialNotification` return a notification when app was opened via notification from Quit state(force closed)
      messaging().getInitialNotification().then((message) => {
        console.log('DEBUG: getInitialNotification', message);

        // TODO: format message and perform some action
      });
    })
      .then(token => this.onTokenReceived(token));

    messaging().setBackgroundMessageHandler(async (message) => {
      console.log('DEBUG: setBackgroundMessageHandler', message);

      // TODO: format message and perform some action
    });
  }

  render() {
    if (this.props.isHeadless) {
      // App has been launched in the background by iOS, ignore
      return null;
    }

    return <RootContainer />;
  }
}

// Run the app
AppRegistry.registerComponent('AwesomeApp', () => HeadlessCheck);


  1. Console output when App was in the Background:
DEBUG: handleAppStateChange background  // last state of the App before press on Push Notification
DEBUG: componentWillUnmount
DEBUG: handleAppStateChange active
DEBUG: componentDidMount
DEBUG: getInitialNotification {notification: {…}, sentTime: 1663170152711, data: {…}, from: '626187936240', messageId: '0:1663170152734013%1dc3dbc31dc3dbc3', …}
  1. Console output when App was in the Foreground:
DEBUG: handleAppStateChange active // last state of the App before press on Push Notification
DEBUG: handleAppStateChange background
DEBUG: handleAppStateChange active
DEBUG: componentDidMount
DEBUG: componentWillUnmount
DEBUG: getInitialNotification {notification: {…}, sentTime: 1663169738077, data: {…}, from: '626187936240', messageId: '0:1663169738090567%1dc3dbc31dc3dbc3', …}

olegmilan avatar Sep 14 '22 15:09 olegmilan

Fascinating - I may not have time to look at this for a little bit, but your reproduction seems like it should isolate the behavior Do you have the JSON that triggers this? (removing device id of course) but that could help

mikehardy avatar Sep 14 '22 16:09 mikehardy

@mikehardy This is the payload we send from our Firebase Connector service to Firebase, thanks

const notificationPayload = {
  to: 'deviceId',
  content_available: true,
  notification: {
    title: 'Notification title',
    body: 'Notification message',
    sound: 'default',
  },
  data: {
    id: 'notificationId',
    userId: 'string',
    //...other notification props
  },
};

and we receive notification in getInitialNotification callback:

{
    "notification": {
      "android": {
        "sound": "default"
      },
      "title": 'Notification title',
      "body": 'Notification message',
    },
    "sentTime": 1663170650172,
    "data": {
      "id": "ID",
      "userId": "ID",
      // ...other props
    },
    "from": "ID",
    "messageId": "FIREBASE MESSAGE ID",
    "ttl": ID,
    "collapseKey": "APP ID/NAME"
}

olegmilan avatar Sep 14 '22 16:09 olegmilan

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 attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Dec 05 '22 19:12 github-actions[bot]

Is this still happening for you? Curious on this one, it has good information in it...

mikehardy avatar Dec 05 '22 19:12 mikehardy

hi @mikehardy, we are still having this issue. We are going to upgrade RN from 0.67 to latest version and some other libraries soon, hopefully this will fix the issue. At least this is an edge case, but a weird one

olegmilan avatar Dec 06 '22 08:12 olegmilan

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 attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Jan 03 '23 08:01 github-actions[bot]

@olegmilan I have run into the same issue. Did your upgrade help in any way?

Our setup is a little different as we use a third party service and library to manage the push but the resulting problem is the same.

SeanArmstrong avatar Jan 09 '23 12:01 SeanArmstrong

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 attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Feb 06 '23 12:02 github-actions[bot]

Ok, so in our case and possible yours when clicking on a notification with the app in the foreground. It was mounting a new component / new app. I think because of a new activity being created on the Android side? And then it was unmounting the old component

class App extends React.Component {

  constructor(props){
    super(props);
    navigationRef = createNavigationContainerRef()

    this.state = { count: 0, identifier: uuid.v4() }

  }

  componentDidMount() {
    console.log("APP MOUNT " + this.state.identifier);
  }

  componentWillUnmount() {
    console.log("APP UNMOUNTING " + this.state.identifier);
  }

  render() {
    return (
      <NavigationContainer ref={navigationRef}>
        <Stack.Navigator>
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Details" component={DetailsScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    )
  }
}
 LOG  Running "App" with {"rootTag":81}
 LOG  APP MOUNT ba7113f3-e3f8-403a-a5ba-d8c79ebf483b
 LOG  PUSH RECEIVED HANDLER
 // PUSH OPENED HERE
 LOG  Running "App" with {"rootTag":91}
 LOG  APP MOUNT cc08aea6-45ac-4bce-8f97-70ac325b2bc2
 LOG  PUSH OPENED HANDLER
 LOG  APP UNMOUNTING ba7113f3-e3f8-403a-a5ba-d8c79ebf483b

SeanArmstrong avatar Feb 09 '23 11:02 SeanArmstrong

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 attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

github-actions[bot] avatar Mar 09 '23 12:03 github-actions[bot]

Ok, so in our case and possible yours when clicking on a notification with the app in the foreground. It was mounting a new component / new app. I think because of a new activity being created on the Android side? And then it was unmounting the old component

class App extends React.Component {

  constructor(props){
    super(props);
    navigationRef = createNavigationContainerRef()

    this.state = { count: 0, identifier: uuid.v4() }

  }

  componentDidMount() {
    console.log("APP MOUNT " + this.state.identifier);
  }

  componentWillUnmount() {
    console.log("APP UNMOUNTING " + this.state.identifier);
  }

  render() {
    return (
      <NavigationContainer ref={navigationRef}>
        <Stack.Navigator>
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Details" component={DetailsScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    )
  }
}
 LOG  Running "App" with {"rootTag":81}
 LOG  APP MOUNT ba7113f3-e3f8-403a-a5ba-d8c79ebf483b
 LOG  PUSH RECEIVED HANDLER
 // PUSH OPENED HERE
 LOG  Running "App" with {"rootTag":91}
 LOG  APP MOUNT cc08aea6-45ac-4bce-8f97-70ac325b2bc2
 LOG  PUSH OPENED HANDLER
 LOG  APP UNMOUNTING ba7113f3-e3f8-403a-a5ba-d8c79ebf483b

thanks for the info @SeanArmstrong. Did you find a way to fix/overcome this issue? We are still experiencing this issue

olegmilan avatar Mar 04 '24 15:03 olegmilan

UPADTE: the problem was, that we had to use android:launchMode="singleTask" in Main in AndroidManifest file which resolved the issue:

<activity
       android:name=".MainActivity"
       android:launchMode="singleTask"
       ...
/>

Hopefully, this will help others cc: @SeanArmstrong @mikehardy

olegmilan avatar Mar 05 '24 13:03 olegmilan