У нас есть приложение 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;```