evollu / react-native-fcm

react native module for firebase cloud messaging and local notification
MIT License
1.73k stars 681 forks source link

double notification ios foreground #900

Open dhcmega opened 6 years ago

dhcmega commented 6 years ago

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 (
      <Provider store={store}>
        <AppNavigator />
      </Provider>
    );
  }
}

./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();
evollu commented 6 years ago

you are calling FCM.presentLocalNotification(options); in the end of notification callback. you don't need to do this. for android killed: only opens app, with no notif info, you can either a. add data in your options of FCM.presentLocalNotification(options); b. use custom notification and remove FCM.presentLocalNotification(options);

dhcmega commented 6 years ago

@evollu thanks! will test it.