evollu / evollu/react-native-fcm

double notification ios foreground

Open
#900 2 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.