firebase notifications stops working after a while (one day or a few days)

1.4k Views Asked by At

I am very frustrated with this problem:(

I am developing an app for android and ios (using capacitor 3) and I am sending notifications to the app via firebase notifications. (capacitor packages: @capacitor-community/fcm and @capacitor/push-notifications).

It works for a while and after one day or a few days that the app is running in background or foreground (and not killed) it stops from working and the app doesn't get notifications(This has happened to me in android device.).

I am sending notifications using topics and i also tried to send the notification through firebase console, but it didn't work.

I am not sure if this means that the registration token has expired because I would think that the capacitor packages are suppose to handle it since they are not talking about this problem.

I did everything from the documentation of capacitor push notifications.

When I watch the logs I can see the next error: Failed to sync topics. Won't retry sync. INVALID_PARAMETERS.

My code in javascript:

import '@capacitor/core';
import { ActionPerformed, PushNotificationSchema, PushNotifications } from '@capacitor/push-notifications'
import { FCM } from '@capacitor-community/fcm';
import { getMessaging, getToken as firebaseGetToken, onMessage, deleteToken, isSupported } from "firebase/messaging";
import { myAxios } from './generic-functions/my-axios';
const platform = window.Capacitor && window.Capacitor.platform;
const topicIos = `${process.env.REACT_APP_TOPIC}_ios`;
const topicAnd = `${process.env.REACT_APP_TOPIC}_and`;

function isCapacitor(): boolean {
    //check if we are in a capacitor platform
    return window.Capacitor && (window.Capacitor.platform === "android" || window.Capacitor.platform === "ios")
}

export async function InitFCM(destination: string) {
    if (!isCapacitor()) {
        const isNtfSupported = await isSupported()
        if (!isNtfSupported) return

        // web notifications
        Notification.requestPermission().then(function (permission) {
            if (permission === 'granted') {
                subscribeTo(destination);
            } else {
                // Show some error
            }
        });

        const messaging = getMessaging();

        onMessage(messaging, (payload) => {
            let notification = payload.data;

            const notificationOptions: NotificationOptions = {
                badge: notification?.largeIco,
                body: notification?.body,
                icon: notification?.largeIcon
            };

            const title = notification?.title || "";

            // show notification
            navigator.serviceWorker
                .getRegistrations()
                .then((registration) => {
                    if (notification?.sound) {
                        const audio = new Audio(`/notifications/${notification?.sound}`)
                        audio.play()
                    }
                    registration[0].showNotification(title, notificationOptions);
                });
        })
        return
    }
    try {
        console.log('Initializing Push Notifications');

        // Request permission to use push notifications
        // iOS will prompt user and return if they granted permission or not
        // Android will just grant without prompting
        PushNotifications.requestPermissions().then(result => {
            if (result.receive === 'granted') {
                // Register with Apple / Google to receive push via APNS/FCM
                // PushNotifications.register();
                subscribeTo(destination);
            } else {
                // Show some error
            }
        });

        // Some issue with our setup and push will not work
        PushNotifications.addListener('registrationError',
            (error: any) => {
                console.log('Error on registration: ' + JSON.stringify(error));
            }
        );

        // Show us the notification payload if the app is open on our device
        PushNotifications.addListener('pushNotificationReceived',
            (notification: PushNotificationSchema) => {
                console.log('Push received: ' + JSON.stringify(notification));
            }
        );

        // Method called when tapping on a notification
        PushNotifications.addListener('pushNotificationActionPerformed',
            (notification: ActionPerformed) => {
                console.log('Push action performed: ' + JSON.stringify(notification));
            }
        );
    } catch (e) {
        console.log('err in push notifications: ', e);
    }
}

async function subscribeTo(destination: string) {
    if (!isCapacitor()) {
        //subscribe to web topic
        const messaging = getMessaging();
        firebaseGetToken(messaging, { vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY }).then(
            async (token) => {
                if (token) {
                    await myAxios.post("/api/notifications/subscribe-to-topic", { token, destination });
                }
            }).catch((err) => {
                console.log('An error occurred while retrieving token. ', err);
            });
        return
    }
    try {
        await PushNotifications.register();
        if (platform === "ios") {
            //subscribe to ios topic
            const resIos = await FCM.subscribeTo({ topic: `${topicIos}_${destination}` });
            console.log(`subscribed to ios Topic ${JSON.stringify(resIos)}`);
        }
        if (platform === "android") {
            //subscribe to android topic
            const resAnd = await FCM.subscribeTo({ topic: `${topicAnd}_${destination}` });
            console.log(`subscribed to android Topic ${JSON.stringify(resAnd)}`);
        }
    } catch (error) {
        console.log(JSON.stringify(error));
    }
}

export async function getToken() {
    try {
        /* const result = */ await FCM.getToken();
        // console.log("TOKEN", result.token);
    } catch (error) {
        console.log(error);
    }
}

export async function unsubscribeFrom(destination?: string) {
    if (!isCapacitor()) {
        const isNtfSupported = await isSupported()
        if (!isNtfSupported || !destination) return
        const messaging = getMessaging();

        //unsubscribe from web topic
        firebaseGetToken(messaging, { vapidKey: process.env.REACT_APP_FIREBASE_VAPID_KEY }).then(
            async (token) => {
                if (token) {
                    await myAxios.post("/api/notifications/unsubscribe-from-topic", { token, destination });
                }
            }).catch((err) => {
                console.log('An error occurred while retrieving token. ', err);
            });

        return
    }
    try {
        await PushNotifications.removeAllListeners();
        if (destination) {
            if (platform === "ios") {
                //unsubscribe from ios topic
                const resIos = await FCM.unsubscribeFrom({ topic: `${topicIos}_${destination}` });
                console.log(`unsubscribed from ios topic ${resIos}`);
            }
            if (platform === "android") {
                //unsubscribe from android topic
                const resAndroid = await FCM.unsubscribeFrom({ topic: `${topicAnd}_${destination}` });
                console.log(`unsubscribed from android topic ${topicAnd}_${destination}: ${resAndroid.message}`);
            }
        }
    } catch (error) {
        console.log(error)
    }
    if (platform === 'android') {
        await FCM.deleteInstance();
    }
}

Thank you all in advanced!

1

There are 1 best solutions below

0
Ton Snoei On

This is a common issue since Android 7.0. The problem occurs because you make use of data messages. This part of your code onMessage(messaging, (payload) => { tells me that you rely on that. This means that when a message is received, your apps code will handle the delivery even when in the background. It will create a notification to show it on the device and play a sound for example.

Power Management taken too far

Several device manufacturers have improved their power management too far. This results in the following problem: After a few days of inactivity, an app is completely killed by the Android OS. This means that the app is not able to handle incoming messages in the background anymore. Vendors have gone too far. But you can't do anything about that.

What to do?

To solve the problem, you should rely on notification messages. These are messages that are directly delivered to the Android OS, instead of your app. This means that messages do not need background handling of your app. On the server (sending) side it means you have to modify your current message and add notification info to the message that is sent.

The drawback

The drawback of notification messages is that you can't lay your hands on the data that is part of the notification. If you previously filled your app with data from each notification, with notification messages, you get the data only when your app is in the foreground or the notification is clicked. To get all data within your app, you need a server API solution or something else.

To overcome this you can add a NotificationListener to your app. I am not sure how to do this in Capacitor. A native example can be found here: https://github.com/Chagall/notification-listener-service-example. The NotificationListener can listen for notifications delivered to the Android device also in the background. With this solution you can be sure notifications are always delivered and the data is delivered in the background. But maybe, I don't know, this listener is killed too by power management. When you use the NotificationListener, you need a special permission, that must be set via device settings (see the mentioned example).

Conclusion

Change from data messages to notification messages. Provide a different way to get the data of your messages in your app. You can use the NotificationListener but I don't know if that is reliable. The most obvious solution is to introduce a server side API that provides the data to your app. In the new situation the notifications are reliable delivered to the app.