evollu / evollu/react-native-fcm
double notification ios foreground
- Dominant language
- Java
- Stars
- 1.7k
- Forks
- 661
- PR merge metrics
- No merged PRs in 30d
Description
Hi
I'm using
RN 0.53.3
FCM 14.0.3
When the app is in foreground I receive double notification, and double alert (alert is part of the app)
I have been testing for days and still cannot find a consistent behavior, mainly because I just don't get it...
Android => foreground: ok, background: ok, killed: only opens app, with no notif info
iOS => foreground: DUPLICATED, background: ok, killed: ok
Thanks!!!
App.js
```
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';
import { AsyncStorage, Platform } from 'react-native';
import SplashScreen from 'react-native-splash-screen';
import store from './store';
import AppNavigator from './navigators/AppNavigator';
import notifications from './utils/notifications';
global.XMLHttpRequest = global.originalXMLHttpRequest ?
global.originalXMLHttpRequest :
global.XMLHttpRequest;
global.FormData = global.originalFormData ?
global.originalFormData :
global.FormData;
notifications.registerKilledListener();
export default class App extends Component<{}> {
constructor(props) {
super(props);
state = {
isRehydrated: false,
token: '',
tokenCopyFeedback: '',
};
}
componentWillMount() {
persistStore(store, { storage: AsyncStorage }, () => {
this.setState({ isRehydrated: true }, this.hideSplash());
});
}
componentDidMount() {
this.configureNotifications();
}
configureNotifications() {
notifications.registerAppListener(this.props.navigation);
notifications.requestPermissions()
.then(() => {
notifications
.getToken()
.then((token) => {
notifications.subscribe('openkey');
console.log(token);
// oSaveFirabaseToken(token);
})
.catch(e => console.log(e));
})
.catch(e => console.log(e));
notifications.setup();
}
hideSplash = () => SplashScreen.hide();
render() {
return (
);
}
}
```
./utils/notifications/index.js
```
/* eslint-disable class-methods-use-this */
import { Platform, Alert, AsyncStorage, AppState } from 'react-native';
import FCM, {
FCMEvent,
RemoteNotificationResult,
WillPresentNotificationResult,
NotificationType,
NotificationActionType,
NotificationActionOption,
NotificationCategoryOption,
} from 'react-native-fcm';
AsyncStorage.getItem('lastNotification').then((data) => {
if (data) {
// if notification arrives when app is killed, it should still be logged here
console.log('last notification', JSON.parse(data));
AsyncStorage.removeItem('lastNotification');
}
});
AsyncStorage.getItem('lastMessage').then((data) => {
if (data) {
// if notification arrives when app is killed, it should still be logged here
console.log('last message', JSON.parse(data));
AsyncStorage.removeItem('lastMessage');
}
});
import commonFunction from '../commonFunction';
let itemMenu = null;
let accountId = null;
let title = null;
let body = null;
class Notifications {
requestPermissions() {
return new Promise((resolve, reject) => {
FCM.requestPermissions({ badge: false, sound: true, alert: true })
.then(() => resolve())
.catch(() => reject());
});
}
setup() {
FCM.getInitialNotification().then(notif => console.log(notif));
}
setNav(nav) {
this.nav = nav;
}
setUseCondominio(doUseCondominio) {
this.doUseCondominio = doUseCondominio;
}
getToken() {
return new Promise((resolve, reject) => {
FCM.getFCMToken().then((token) => {
console.log('FCM TOKEN: ', token);
if (token) {
resolve(token);
} else {
reject();
}
});
});
}
getAPNSToken() {
return new Promise((resolve, reject) => {
FCM.getAPNSToken().then((token) => {
console.log('APNS TOKEN (getFCMToken)', token);
if (token) {
resolve(token);
} else {
reject();
}
});
});
}
registerKilledListener() {
// these callback will be triggered even when app is killed
FCM.on(FCMEvent.Notification, (notif) => {
AsyncStorage.setItem('lastNotification', JSON.stringify(notif));
if (notif.opened_from_tray) {
setTimeout(() => {
if (notif._actionIdentifier === 'reply') {
if (AppState.currentState !== 'background') {
console.log(`User replied ${JSON.stringify(notif._userText)}`);
alert(`User replied ${JSON.stringify(notif._userText)}`);
} else {
AsyncStorage.setItem('lastMessage', JSON.stringify(notif._userText));
}
}
if (notif._actionIdentifier === 'view') {
// alert('User clicked View in App');
}
if (notif._actionIdentifier === 'dismiss') {
// alert('User clicked Dismiss');
}
}, 1000);
}
});
}
// these callback will be triggered only when app is foreground or background
registerAppListener(navigation) {
FCM.on(FCMEvent.Notification, this.handlerNotificactions.bind(this));
FCM.on(FCMEvent.RefreshToken, (token) => {
console.log('TOKEN (refreshUnsubscribe)', token);
});
FCM.enableDirectChannel();
FCM.on(FCMEvent.DirectChannelConnectionChanged, (data) => {
console.log(`direct channel connected: ${data}`);
});
setTimeout(() => {
FCM.isDirectChannelEstablished().then(d => console.log(d)).catch(e => console.log(e));
}, 1000);
}
handlerNotificactions(notif) {
console.log('Notification', notif);
if (!notif) {
return;
}
if (Platform.OS === 'ios' && notif._notificationType === NotificationType.WillPresent && !notif.local_notification) {
// this notification is only to decide if you want to show the notification when user if in foreground.
// usually you can ignore it. just decide to show or not.
console.log(WillPresentNotificationResult.All);
notif.finish(WillPresentNotificationResult.All);
return;
}
if (notif.screen) {
try {
itemMenu = JSON.parse(notif.screen);
accountId = notif.account_id;
title = notif.title;
body = notif.message;
}
catch (e) {
itemMenu = JSON.parse("{'type':'node'}");
}
}
if (notif.opened_from_tray) {
console.log('opened_from_tray');
setTimeout(() => {
if (itemMenu && accountId) {
this.doUseCondominio(accountId * 1);
commonFunction.actionMenu(itemMenu, this.nav);
Alert.alert(title, body);
itemMenu = null;
}
}, 500);
return;
}
if (notif.local_notification) {
console.log('this is a local notification');
return;
}
if (Platform.OS === 'ios') {
// optional
// iOS requires developers to call completionHandler to end notification process. If you do not call it your background remote notifications could be throttled, to read more about it see the above documentation link.
// This library handles it for you automatically with default behavior (for remote notification, finish with NoData; for WillPresent, finish depend on "show_in_foreground"). However if you want to return different result, follow the following code to override
// notif._notificationType is available for iOS platfrom
switch (notif._notificationType) {
case NotificationType.Remote:
notif.finish(RemoteNotificationResult.NewData); // other types available: RemoteNotificationResult.NewData, RemoteNotificationResult.ResultFailed
break;
case NotificationType.NotificationResponse:
notif.finish();
break;
case NotificationType.WillPresent:
notif.finish(WillPresentNotificationResult.All); // other types available: WillPresentNotificationResult.None
// this type of notificaiton will be called only when you are in foreground.
// if it is a remote notification, don't do any app logic here. Another notification callback will be triggered with type NotificationType.Remote
break;
}
}
let options = {
priority: 'high',
show_in_foreground: true,
lights: true,
content_available: true,
sound: 'default',
};
if (Platform.OS === 'ios') {
options = {
...options,
title: notif.title,
body: notif.body,
};
} else {
options = {
...options,
title: notif.fcm ? notif.fcm.title : notif.title,
body: notif.fcm ? notif.fcm.body : notif.body,
vibrate: 300, // Android only default: 300, no vibration if you pass 0
wake_screen: true,
large_icon: 'ic_launcher',
small_icon: 'ic_launcher',
icon: 'ic_notif',
};
}
// con esto anda en fore en ios (no se si en android tambien)
if (itemMenu && accountId) {
this.doUseCondominio(accountId * 1);
commonFunction.actionMenu(itemMenu, this.nav);
Alert.alert(title, body);
itemMenu = null;
}
// asi resetea el contador de notificaciones que va sobre el icono del escritorio
FCM.removeAllDeliveredNotifications();
FCM.presentLocalNotification(options);
}
subscribe(topic) {
console.log(`subscribe: ${topic}`);
FCM.unsubscribeFromTopic(topic);
FCM.subscribeToTopic(topic);
this.enabled = true;
}
unsubscribe(topic) {
console.log(`unsubscribe: ${topic}`);
FCM.unsubscribeFromTopic(topic);
this.enabled = false;
}
}
export default new Notifications();
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with App.js and utils/notifications/index.js, especially registerKilledListener, registerAppListener, and handlerNotificactions. Trace the iOS foreground callbacks and the subsequent FCM.presentLocalNotification call, then reproduce the foreground case. Done means one notification and one app alert are produced on iOS without breaking the reported Android or background behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, javascript
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100