Understanding push notifications code structure

Hello, I hope you’re all doing well
I managed to get pushed notifications on an emulator from firebase, registering on the app, getting the token, having my api prompt the firebase endpoint and back to the app again.

The thing I can’t quite understand and not finding clear material on is where do I put the code for adding listeners and registering for token.
For now I had them in my App.vue onMounted but now moved them to a button click (for testing),
If I do leave them in main.ts for example or the mounting of any page/component, wouldn’t every time the user opens the app or opens that page the app would run the code again and getting a different/same code needlessly?
Is that the way it’s supposed to be always getting a different token for each use and re-registering it in my api for this current user? Which I’m sure is not the case as it doesn’t make much sense.

I really could use some help on wrapping my head around this so I understand it better.

Thank you kindly, I hope you’re having a good day
Regards

The push notification token can change at any time, so it’s recommended to always call register on app startup to see if it changed.

For the listeners it depends in your app, some people only want to listen in certain cases, but if you want them to always fire you have to put them on app startup too, javascript code execution doesn’t persist after the app is killed, despite the native code for the listeners will still be fired, if there is no javascript code listening for them your javascript code will not be notified.

1 Like

Thank you for your reply,

I see, Should I store the token locally and check every time I load the app, If the registration differs, I update my api with the new one?

Also, is the token generated by the device or is it because of the firebase config and the plugin automates all that noise but eventually gets a token from firebase?

Excuse the questions, this is my first time working with firebase and cloud messaging, Thank you Julio

The token is generated and provided by firebase, not sure if the SDK does it locally or it takes part on their servers.

Google recommends storing them in your server (not locally) with a timestamp, so you know last time the app was used.

Also when you send a notification from your server to multiple devices, google servers return you the tokens what are no longer available, so you have to remove those from your server to not try to send notifications to them in the future since they are now invalid.

1 Like

I store it locally in the app and in the backend. Every time the app opens, I call PushNotifications.register() which then triggers the registration listener if successful. Within the listener, I check if the current token matches what is stored locally. I only send it to my backend (API) if the token is different/has changed or if my app hasn’t sent it to the backend in the last month.

1 Like

Oh man this is gold, Thank you both very much this would’ve taken me too long to figure out how to do properly.
Have a good day fellas

1 Like

Hi,

Sorry to revive this post, but i also working with Push Notifications and I got some questions regarding the initiation or registration. For example i have it right now in my AppComponent, but what if in my app i have a login page, I wont want to register that user with the token. Can I have it in more than one place or what would you guys suggets? Thanks

What I do is call requestPermissions only after the user has logged in. And don’t call register unless permission has been granted.

I also only call my update logic if the user is logged in. That method only calls register if the user has granted permission.

1 Like

Could you show me an example if you can please.

I’ve created wrappers around the Capacitor plugin methods. Here is my code (I use Vue).

On app boot (called in main.ts in a Vue app):

PushNotifications.initialize() // In push-notifications.ts

push-notifications.ts

This probably could have been refactored into my-push-notifications.ts :upside_down_face:

import { MyPushNotifications } from '@/notifications/my-push-notifications'
import router from '@/router'
import { useAppStore } from '@/stores/app-store'

export class PushNotifications {
    public static initialize(): void {
        MyPushNotifications.initialize()

        router.isReady().then(() => {
            const store = useAppStore()

            if (store.isLoggedIn) {
                MyPushNotifications.updateToken()
            }
        })
    }
}

my-push-notifications.ts

import { toastControllerInstance } from '@/instances'
import { useAppStore } from '@/stores/app-store'
import { DateTimeHelper } from '@/utils/date-time-helper'
import { DeviceInfoHelper } from '@/utils/device-info-helper'
import { ToastType } from '@/utils/toast-controller'
import { PermissionState } from '@capacitor/core'
import {
    ActionPerformed,
    PushNotifications,
    PushNotificationSchema,
    Token,
} from '@capacitor/push-notifications'

export class MyPushNotifications {
    public static async initialize(): Promise<void> {
        if (!DeviceInfoHelper.isMobileApp()) {
            return
        }

        await this.clearListeners()
        this.addListeners()
        this.addChannels()
    }

    public static async requestPermissions(): Promise<PermissionState> {
        if (!DeviceInfoHelper.isMobileApp()) {
            return 'granted'
        }

        const requestResult = await PushNotifications.requestPermissions()

        if (requestResult.receive === 'granted') {
            this.register()
        }

        return requestResult.receive
    }

    public static async register(): Promise<void> {
        await PushNotifications.register()
    }

    public static async updateToken(): Promise<void> {
        if (!DeviceInfoHelper.isMobileApp()) {
            return
        }

        if (await this.hasPermission()) {
            this.register()
        }
    }

    public static async getPermissionStatus(): Promise<PermissionState> {
        return (await PushNotifications.checkPermissions()).receive
    }

    public static async hasPermission(): Promise<boolean> {
        if ((await MyPushNotifications.getPermissionStatus()) === 'granted') {
            return true
        }

        return false
    }

    public static clearTokenFromStore(): void {
        const appStore = useAppStore()

        appStore.setPushNotificationToken({
            token: '',
            lastUpdated: DateTimeHelper.minDate(),
        })
    }

    private static async clearListeners(): Promise<void> {
        await PushNotifications.removeAllListeners()
    }

    private static async addListeners(): Promise<void> {
        await PushNotifications.addListener('registration', (token: Token) => {
            const appStore = useAppStore()

            if (!appStore.isLoggedIn) {
                return
            }

            if (
                token.value !== appStore.pushNotificationToken.token ||
                DateTimeHelper.isTimeToUpdate(
                    appStore.pushNotificationToken.lastUpdated,
                    2419200, // 4 weeks
                )
            ) {
                appStore.savePushNotificationToken(token.value)
            }
        })

        await PushNotifications.addListener('registrationError', () => {
            toastControllerInstance.createWithJustMessage(
                'There was an error registering push notifications',
                ToastType.Error,
            )
        })

        // Listens for notifications when the app is in the foreground
        await PushNotifications.addListener(
            'pushNotificationReceived',
            async (notification: PushNotificationSchema) => {
                // Stuff
            },
        )

        // Method called when tapping on a notification
        await PushNotifications.addListener(
            'pushNotificationActionPerformed',
            (notification: ActionPerformed) => {
                if (notification.actionId === 'tap') {
                    // Stuff
                }
            },
        )
    }

    private static addChannels(): void {
        PushNotifications.createChannel({
            id: 'example',
            name: 'example',
            description: 'Example',
            importance: 3,
        })

        PushNotifications.createChannel({
            id: 'example1',
            name: 'example1',
            description: 'Example 1',
            importance: 3,
        })
    }
}

On account creation or login, the following gets called:

// Clear push notification token to force a refresh
MyPushNotifications.clearTokenFromStore()
MyPushNotifications.updateToken()

We call MyPushNotifications.requestPermissions() after the user creates an account or logins in. We call it from a button within a modal that pops up explaining why the user should grant push notifications. This modal only shows if they haven’t already granted/rejected permission by calling MyPushNotifications.getPermissionStatus().

2 Likes