iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Push Notifications

Push notifications are the standard way to bring users back into your app. Capacitor + Firebase Cloud Messaging (FCM) gives you one cross-platform API; the configuration story differs per platform but the runtime code is shared.

FCM, plugin, permissions, payload, server

EXAMPLE
// 1) Install
// npm install @capacitor/push-notifications
// npx cap sync

import { PushNotifications, Token, PushNotificationSchema } from '@capacitor/push-notifications';
import { Capacitor } from '@capacitor/core';

// 2) Permission request + listener registration
async function registerForPush() {
    if (!Capacitor.isNativePlatform()) return;     // web fallback handled separately

    let perm = await PushNotifications.checkPermissions();
    if (perm.receive !== 'granted') {
        perm = await PushNotifications.requestPermissions();
        if (perm.receive !== 'granted') {
            console.warn('push declined');
            return;
        }
    }

    await PushNotifications.register();             // gets token from FCM/APNs
}

// 3) Listener events
PushNotifications.addListener('registration', (token: Token) => {
    // Send token to your backend
    saveDeviceToken(token.value);
});

PushNotifications.addListener('registrationError', (err) => {
    console.error('FCM/APNs registration failed', err);
});

PushNotifications.addListener('pushNotificationReceived', (n: PushNotificationSchema) => {
    // Foreground notification — show in-app banner
    console.log('foreground push', n);
});

PushNotifications.addListener('pushNotificationActionPerformed', (action) => {
    // User tapped a notification
    const { route } = action.notification.data;
    if (route) router.navigate(route);
});

// 4) Backend — Firebase Admin SDK send
import { initializeApp } from 'firebase-admin/app';
import { getMessaging } from 'firebase-admin/messaging';

initializeApp();

async function sendToToken(token: string, title: string, body: string, data: Record<string, string>) {
    return getMessaging().send({
        token,
        notification: { title, body },
        data,                                            // string-only key/values
        android: { notification: { channelId: 'default', priority: 'high' } },
        apns: {
            payload: {
                aps: { 'mutable-content': 1, sound: 'default', badge: 1 },
            },
        },
        webpush: {
            notification: { icon: '/icon-192.png' },
        },
    });
}

// 5) Topic-based broadcast
await getMessaging().subscribeToTopic([token1, token2], 'news');
await getMessaging().send({
    topic: 'news',
    notification: { title: 'Breaking', body: 'Big news' },
});

// 6) iOS — APNs setup (one-time)
// • Apple Developer: create an APNs key (.p8) + Team ID
// • Firebase Console → Project Settings → Cloud Messaging → upload APNs key
// • Xcode: enable 'Push Notifications' capability
// • Xcode: enable 'Background Modes' → 'Remote notifications'
// • Info.plist: no extra entry needed (handled by capability)

// 7) Android — FCM setup (one-time)
// • Firebase Console → Add Android app → download google-services.json into android/app
// • android/build.gradle: classpath 'com.google.gms:google-services:4.4.x'
// • android/app/build.gradle: apply plugin: 'com.google.gms.google-services'
// • Optional: configure default notification channel + icon resource

// AndroidManifest.xml (Capacitor 5+ creates this):
// <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
//            android:value="default" />
// <meta-data android:name="com.google.firebase.messaging.default_notification_icon"
//            android:resource="@drawable/ic_notification" />

// 8) Notification channels (Android 8+)
import { LocalNotifications } from '@capacitor/local-notifications';
await LocalNotifications.createChannel({
    id: 'default',
    name: 'Default',
    importance: 4,
    visibility: 1,
    sound: 'default',
});

// 9) Foreground vs background
// • Foreground: pushNotificationReceived fires; you decide whether to show UI (use Local Notifications to display)
// • Background: OS shows the notification; pushNotificationActionPerformed fires on tap
// • Killed: same as background; payload includes 'notification' field — OS handles display

// 10) Web push
// Web push uses a service worker + VAPID keys.
// • Generate VAPID via firebase-admin
// • Register service worker, subscribe to PushManager
// • Forward subscription to your backend
// Capacitor's plugin doesn't cover web; use the Firebase JS SDK directly:
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
const token = await getToken(messaging, { vapidKey: 'BNkR...' });

onMessage(messaging, (payload) => {
    new Notification(payload.notification.title, { body: payload.notification.body });
});

// 11) Payload size + deep links
// • Max payload ≈ 4 KB. Keep extra data minimal.
// • For deep linking, set 'data.route' or 'data.deep_link' on the payload; parse in pushNotificationActionPerformed.

// 12) Server tokens — manage carefully
// • Tokens rotate; backend MUST persist by user + device id and REPLACE on registration callbacks
// • Remove tokens that come back as 'registration-token-not-registered' from FCM
// • Don't ship FCM server keys in client code — keep on backend only

// 13) UX patterns
// • Don't ask for permission immediately on first launch — explain why first (rationale screen)
// • Provide a Settings toggle to opt out without app permissions UI
// • Group related notifications via 'tag' / 'collapse_key'
// • Quiet hours — respect time zone
// • Localise the payload server-side

// 14) Testing
// • Use Firebase Console → Cloud Messaging → 'New campaign' for ad-hoc sends
// • Or curl directly:
curl -X POST https://fcm.googleapis.com/v1/projects/PROJECT/messages:send \\
     -H "Authorization: Bearer $ACCESS" \\
     -H "Content-Type: application/json" \\
     -d '{"message":{"token":"...","notification":{"title":"Hi","body":"World"}}}'

// 15) Common bugs
// • Missing APNs key → iOS device registers but no notifications received
// • App killed but notifications missing → OS battery optimisations; vendor-specific (especially Xiaomi/OPPO)
// • Token saved but stale → user reinstalls; old token still in DB; clean on send failure
// • Notification icon on Android shows as a white square → drawable not configured / Android 5+ requires monochrome
// • Asking permission too early → users decline; explain first
// • Background data-only messages on iOS need 'content-available': 1 — and even then are throttled
// • Topic subscription cache stuck → call unsubscribeFromTopic + resubscribe
// • Web Service Worker not registered → PushManager fails silently in the console

Why it matters

Capacitor + Firebase Cloud Messaging is the standard push stack on Ionic. Request permission with a rationale screen first, send the device token to your backend on the registration event, handle taps via pushNotificationActionPerformed, and configure APNs (iOS) + google-services.json (Android) once. Keep server keys on the server.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { PushNotifications } from '@capacitor/push-notifications';
await PushNotifications.requestPermissions();
await PushNotifications.register();
PushNotifications.addListener('registration', t => console.log(t.value));
Try it Yourself »

Test yourself

Q1. On Android, push uses…
Q2. On iOS, push uses…
Q3. You must call before listening for pushes…

Discussion

Loading…