react-native-firebase
react-native-firebase copied to clipboard
[🐛] Android: ReactNative root component is getting unmounted when open PushNotification when app is in Foreground
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:
App componentDidMountis calledApp componentWillUnmountis 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:
App componentWillUnmountis calledApp componentDidMountis 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.settingsjetifier=truefor Android compatibility? - [x] I am using the NPM package
jetifierfor 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-firebaseversion you're using that has this issue:15.3.0
Firebasemodule(s) you're using that has the issue:@react-native-firebase/messaging
- Are you using
TypeScript?N
- 👉 Check out
React Native FirebaseandInvertaseon Twitter for updates on the library.
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
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);
- 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', …}
- 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', …}
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 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"
}
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.
Is this still happening for you? Curious on this one, it has good information in it...
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
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.
@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.
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.
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
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.
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
UPADTE:
the problem was, that we had to use android:launchMode="singleTask" in Main
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
...
/>
Hopefully, this will help others cc: @SeanArmstrong @mikehardy