React Native Firebase Уведомления не всегда отображаются - PullRequest
2 голосов
/ 30 января 2020

У нас есть приложение React-Native на iOS и Android. Мы используем RN 0.61.5 и response-native-firebase 5.5.6.

Мы используем response-native-firebase / notifications для добавления локальных уведомлений на устройстве. Когда мы проверяем это, эти уведомления планируются правильно, но мы получаем отзывы от некоторых наших пользователей, что они не получают уведомления. Я добавил наш файл notifications.ts и наш файл NotificationListener.tsx ниже.

У кого-нибудь есть идея, возможная причина советов по отладке / улучшению / исправлению наших уведомлений?

Код

notifications.ts:

// Firebase Notifications
// ---
// Custom implementation on top of the Firebase Notifications library.
// This class is responsable for creating, scheduling and re-scheduling notifications.
// ------------------------------------------------------------------------- /

import firebase from "react-native-firebase";
import { Notification } from "react-native-firebase/notifications";
import { Recycle } from "@shd-types/fp-recycle-api";
import { isEmpty } from "ramda";

import { Store } from "../../store";
import { IS_ANDROID, IS_IOS } from "../../utils/device-info";
import i18n, { manualTranslate } from "../../utils/i18n";

import { NotificationsState } from "../../features/notifications/store/notifications.types";
import { CollectionRoundsState } from "../../features/collection-rounds/store/collectionRounds.types";
import { ProfileState } from "../../features/profile/store/profile.types";

const MAX_ALLOWED_NOTIFICATIONS = 64;

class FirebaseNotifications {

    private permissionsGranted: boolean = false;
    private language: string;
    private notificationState: NotificationsState;
    private collectionRoundsState: CollectionRoundsState;
    private profileState: ProfileState;

    constructor() {
        // Ask for permission to send notifications
        this.init();

        const store: any = Store.getState();
        this.language = store.global.language;
        this.notificationState = store.notifications;
        this.collectionRoundsState = store.collectionRounds;
        this.profileState = store.profile;

        // Create an Android Notification Channel
        // More info on this here: https://rnfirebase.io/docs/v5.x.x/notifications/android-channels
        this.initChannels();
    }

    async init(): Promise<void> {
        const store: any = Store.getState();
        this.language = store.global.language;
        this.notificationState = store.notifications;
        this.collectionRoundsState = store.collectionRounds;
        this.profileState = store.profile;

        if (this.permissionsGranted) {
            return Promise.resolve();
        }

        const enabled = await firebase.messaging().hasPermission();

        if (!enabled) {
            try {
                await firebase.messaging().requestPermission();
                this.permissionsGranted = true;
            } catch (error) {
                // User has rejected permissions
                this.permissionsGranted = false;
                return Promise.reject();
            }
        } else {
            this.permissionsGranted = true;
        }

        return Promise.resolve();
    }

    async initChannels() {
        // Create the channel groups
        await firebase.notifications().android.createChannelGroups([
            // Main channel group
            new firebase.notifications.Android.ChannelGroup("general", "General"),

            // Channel group for each address
            new firebase.notifications.Android.ChannelGroup("collections-home", "Home"),
        ]);

        // Build a channel
        const channel = new firebase.notifications.Android.Channel("collection-rounds-channel", "Collection Rounds", firebase.notifications.Android.Importance.Max)
            .setDescription("Notifications for Collection Rounds")
            .setGroup("collections-home");

        // Create the channel
        firebase.notifications().android.createChannel(channel);
    }

    // Collection Build Methods
    // ------------------------------------------------------------------------- /
    setAndroidNotificationProps(notification: Notification, channelId?: string, group?: string) {
        // https://rnfirebase.io/docs/v5.x.x/notifications/reference/AndroidNotification
        notification
            .android.setLocalOnly(true)
            .android.setPriority(firebase.notifications.Android.Priority.Max)

            // Notification Style
            .android.setColor("green") // Sets the accent color to use
            .android.setVibrate([500, 500]) // Set notification vibration pattern.
            .android.setLights(0xFF97E92A, 500, 500) // Set notification light to green.
            .android.setSmallIcon("ic_launcher");


        if (channelId) {
            notification
                .android.setChannelId(channelId);
        }
        if (group) {
            notification
                .android.setGroup(group)
                .android.setGroupAlertBehaviour(firebase.notifications.Android.GroupAlert.All);
        }
    }

    setIOSNotificationProps(notification: Notification, category?: string) {
        // https://rnfirebase.io/docs/v5.x.x/notifications/reference/AndroidNotification
        notification
            .ios.setHasAction(false); // IOS 9 only.

        if (category) {
            notification
                .ios.setCategory(category);
        }
    }

    buildOpenAppNotification(fireDate: number): Notification {
        const notification = new firebase.notifications.Notification()
            .setNotificationId(`general-open-app-reminder-${fireDate}`)
            .setTitle(i18n._("notifications.appRefresh.title"))
            .setBody(i18n._("notifications.appRefresh.body"))
            .setSound("default")
            .setData({
                schedule: {
                    fireDate,
                },
            });

        if (IS_ANDROID) {
            this.setAndroidNotificationProps(notification, "general");
        }

        if (IS_IOS) {
            this.setIOSNotificationProps(notification, "general");
            notification
                .ios.setBadge(1);
        }

        return notification;
    }

    buildCollectionNotification(collection: Recycle.Collection.CollectionRound, fireDate?: number, sameDay = false): Notification {
        const collectionTypeNames = collection.types.map((collectionType: Recycle.Type.Item) => manualTranslate(collectionType.name, this.language));

        const notificationTitle = sameDay
            ? `${i18n._("notifications.collectionSameDay.title")}: ${collectionTypeNames.join(", ")}`
            : `${i18n._("notifications.collectionDayBefore.title")}: ${collectionTypeNames.join(", ")}`;

        // TODO: Map this to the location, if multiple
        const notificationSubtitle = i18n._(this.profileState.address[0].alias);

        const notificationBody = sameDay
            ? i18n._("notifications.collectionSameDay.body")
            : i18n._("notifications.collectionDayBefore.body");

        const notificationBodyExtended = sameDay
            ? i18n._("notifications.collectionSameDay.bodyExtended")
            : i18n._("notifications.collectionDayBefore.bodyExtended");

        const notification = new firebase.notifications.Notification()
            .setNotificationId(`notification-collection-${collection.id}-${fireDate}`)
            .setTitle(notificationTitle)
            .setSubtitle(notificationSubtitle)
            .setBody(notificationBody)
            .setData({
                ...collection,
                schedule: {
                    fireDate,
                },
            })
            .setSound("default");

        if (IS_ANDROID) {
            this.setAndroidNotificationProps(notification, "collection-rounds-channel", "collections");
            notification.android
                .setBigText(
                    notificationBodyExtended,
                    notificationTitle,
                    notificationBody
                );
        }

        if (IS_IOS) {
            this.setIOSNotificationProps(notification, "collections");
        }

        return notification;
    }

    resetNotificationBadge() {
        if (IS_IOS) {
            const notifications = firebase.notifications();
            notifications.setBadge(0);
        }
    }

    // Scheduling Methods
    // ------------------------------------------------------------------------- /
    async rescheduleNotifications(collections?: Recycle.Collection.CollectionRound[]) {
        // Clear all scheduled notifications
        const notifications = await firebase.notifications().getScheduledNotifications();

        if (notifications) {
            notifications
                .forEach((notification: Notification) => {
                    firebase.notifications().cancelNotification(notification.notificationId);
                });
        }

        collections = collections ? collections : this.collectionRoundsState.data;

        if (!collections) {
            return;
        }

        // Check if the user has given permission to receive notifications
        try {
            await this.init();
        } catch (err) {
            return;
        }

        // Fetch the notificationsSettings
        const homeNotificationSettings = this.notificationState.settings.home;

        // TODO
        // 1. Account for the amount of notifications (e.g. the day before AND the current day)
        // 2. Account for the amount of addresses to schedule for.
        //
        // We will simulate this for now. (simplified)
        const numberOfNotificationsPerCollection = Object.values(homeNotificationSettings.time).filter((timeSetting) => timeSetting.enabled).length;
        const numberOfAddresses = 1;
        const numberOfCollectionsToSchedule = Math.floor(MAX_ALLOWED_NOTIFICATIONS / (numberOfAddresses * numberOfNotificationsPerCollection)) - 1;

        // TODO
        // 1. Account for the time when we want the notification.

        let lastNotificationTime = 0;

        let collectionTypesToNotNotify: string[] = [];

        // Filter out the collectionTypes we don't want a notification for.
        if (homeNotificationSettings) {
            collectionTypesToNotNotify = Object.values(homeNotificationSettings.categories)
                .filter((category) => !category.enabled )
                .map((category) => category.key);
        }

        // Reschedule all collections
        collections
            .filter((collection) => {
                // Filter out collections that have passed
                if (new Date(collection.date).valueOf() < new Date().valueOf()) {
                    return false;
                }

                // Filter out collections that are not wanted
                if (collectionTypesToNotNotify.length) {
                    const allow = [];
                    const localCollectionTypes = collection.types.map((type) => type.id);

                    for (const collectionType of localCollectionTypes) {
                        allow.push(!collectionTypesToNotNotify.includes(collectionType));
                    }

                    // The collection has at least 1 item we want notifications for.
                    return allow.includes(true);
                }

                return true;
            })
            .map((collection) => {
                // Filter out the collections the user does not want.
                if (!collectionTypesToNotNotify.length) {
                    return collection;
                }

                const localCollectionTypes = collection.types.map((type) => type.id);

                return {
                    ...collection,
                    types: localCollectionTypes.filter((collectionType) => {
                        return !collectionTypesToNotNotify.includes(collectionType);
                    }),
                };
            })
            .slice(0, numberOfCollectionsToSchedule)
            .forEach((collection, index, arr) => {
                const collectionDay = new Date(collection.date);

                let collectionNotificationTime = 0;
                let collectionHour = 0;
                let collectionMinutes = 0;

                if (homeNotificationSettings.time.dayBefore.enabled) {
                    // Get the day before
                    const collectionNotificationDate = new Date(collectionDay).setDate(collectionDay.getDate() - 1);

                    // Set the notification hour
                    collectionHour = homeNotificationSettings.time.dayBefore.hours;
                    collectionMinutes = homeNotificationSettings.time.dayBefore.minutes;
                    collectionNotificationTime = new Date(collectionNotificationDate).setHours(collectionHour, collectionMinutes);

                    // Schedule the notification if it triggers in the future
                    this.scheduleCollectionNotification(collection, collectionNotificationTime);
                }

                if (homeNotificationSettings.time.dayOf.enabled) {
                    // Set the notification hour
                    collectionHour = homeNotificationSettings.time.dayOf.hours;
                    collectionMinutes = homeNotificationSettings.time.dayOf.minutes;
                    collectionNotificationTime = new Date(collectionDay).setHours(collectionHour, collectionMinutes);

                    // Schedule the notification if it triggers in the future
                    this.scheduleCollectionNotification(collection, collectionNotificationTime, true);
                }

                // Schedule the "open your app please" notification after 10 notifcations
                // And after the last one
                if ((index !== 0 && index % 10 === 0) || (index === arr.length - 1)) {
                    // Plan the reminder 1 day after the previous notification
                    const collectionDate = new Date(collectionDay).setDate(collectionDay.getDate() + 1);
                    lastNotificationTime = new Date(collectionDate).setHours(collectionHour, collectionMinutes);
                    firebase.notifications().scheduleNotification(
                        this.buildOpenAppNotification(lastNotificationTime),
                        { fireDate: lastNotificationTime }
                    );
                }
            });
    }

    scheduleCollectionNotification(collection: Recycle.Collection.CollectionRound, fireDate: number, sameDay = false) {
        // Schedule the notification if it triggers in the future
        if (fireDate > new Date().valueOf() && !isEmpty(this.notificationState.settings.home.categories)) {
            firebase.notifications().scheduleNotification(
                this.buildCollectionNotification(collection, fireDate, sameDay),
                { fireDate }
            );
        }
    }
}

export default new FirebaseNotifications();

NotificationListener.tsx:

import React from "react";
import firebase from "react-native-firebase";
import { Notification, NotificationOpen } from "react-native-firebase/notifications";

interface Props {
    children: any;
}

class NotificationListener extends React.Component<Props> {

    private notificationDisplayedListener: (notification?: Notification) => void;
    private notificationListener: (notification?: Notification) => void;
    private notificationOpenedListener: (notificationOpen?: NotificationOpen) => void;

    constructor(props: Props) {
        super(props);

        this.notificationDisplayedListener = firebase.notifications().onNotificationDisplayed(this.onNotificationDisplayed);
        this.notificationListener = firebase.notifications().onNotification(this.onNotification);
        this.notificationOpenedListener = firebase.notifications().onNotificationOpened(this.onNotificationOpened);

        this.onNotificationDisplayed = this.onNotificationDisplayed.bind(this);
        this.onNotification = this.onNotification.bind(this);
        this.onNotificationOpened = this.onNotificationOpened.bind(this);
    }

    // Lifecycle Methods
    // ------------------------------------------------------------------------- /
    // https://rnfirebase.io/docs/v5.x.x/notifications/receiving-notifications#App-Closed
    async componentDidMount() {
        const notificationOpen: NotificationOpen = await firebase.notifications().getInitialNotification();
        if (notificationOpen) {
            // App was opened by a notification
            this.onNotificationOpened(notificationOpen);
        }
    }
    componentWillUnmount() {
        this.notificationDisplayedListener();
        this.notificationListener();
        this.notificationOpenedListener();
    }


    // Callback Methods
    // ------------------------------------------------------------------------- /

    // Triggered when a particular notification has been displayed
    onNotificationDisplayed(notification: Notification) {
        // ANDROID: Remote notifications do not contain the channel ID. You will have to specify this manually if you'd like to re-display the notification.

        // Leave this for debugging purposes
        // console.log("Notification Displayed!:", notification);
    }

    // Triggered when a particular notification has been received, but not displayed
    onNotification(notification: Notification) {
        // Leave this for debugging purposes
        // console.log("Notification received, but not displayed!:", notification);

        // Force the notification in the OS NotificationBar
        firebase.notifications().displayNotification(notification);
    }

    // Triggered when a particular notification has been clicked / tapped / opened whent he app is in the foreground or background.
    onNotificationOpened(notificationOpen: NotificationOpen) {
        // Get the action triggered by the notification being opened
        const action = notificationOpen.action;
        // Get information about the notification that was opened
        const notification: Notification = notificationOpen.notification;

        // Leave this for debugging purposes
        // console.log("Notification Opened!:", notificationOpen);

        // Remove the notification once it has been openend.
        firebase.notifications().removeDeliveredNotification(notificationOpen.notification.notificationId);
    }

    // Render Methods
    // ------------------------------------------------------------------------- /
    render() {
        return this.props.children;
    }
}

export default NotificationListener;```

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...