Loading...
Loading...
Complete guide to implementing push notifications in Capacitor apps using Firebase Cloud Messaging (FCM) and Apple Push Notification Service (APNs). Covers setup, handling, and best practices. Use this skill when users need to add push notifications.
npx skill4agent add cap-go/capgo-skills capacitor-push-notificationsbun add @capacitor/push-notifications
bunx cap syncimport { PushNotifications } from '@capacitor/push-notifications';
async function initPushNotifications() {
// Request permission
const permission = await PushNotifications.requestPermissions();
if (permission.receive === 'granted') {
// Register for push
await PushNotifications.register();
}
// Get FCM token
PushNotifications.addListener('registration', (token) => {
console.log('Push token:', token.value);
// Send token to your server
sendTokenToServer(token.value);
});
// Handle registration error
PushNotifications.addListener('registrationError', (error) => {
console.error('Registration error:', error);
});
// Handle incoming notification (foreground)
PushNotifications.addListener('pushNotificationReceived', (notification) => {
console.log('Notification received:', notification);
// Show in-app notification
showInAppNotification(notification);
});
// Handle notification tap
PushNotifications.addListener('pushNotificationActionPerformed', (action) => {
console.log('Notification action:', action);
// Navigate based on notification data
handleNotificationTap(action.notification);
});
}google-services.jsonandroid/app/// android/build.gradle
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.0'
}
}// android/app/build.gradle
apply plugin: 'com.google.gms.google-services'
dependencies {
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation 'com.google.firebase:firebase-messaging'
}GoogleService-Info.plistios/App/App/# ios/App/Podfile
pod 'Firebase/Messaging'// ios/App/App/AppDelegate.swift
import Firebase
import FirebaseMessaging
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
return true
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
Messaging.messaging().apnsToken = deviceToken
}
}import admin from 'firebase-admin';
// Initialize
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
// Send to single device
async function sendToDevice(token: string) {
await admin.messaging().send({
token,
notification: {
title: 'Hello!',
body: 'You have a new message',
},
data: {
type: 'message',
messageId: '123',
},
android: {
priority: 'high',
notification: {
channelId: 'messages',
icon: 'ic_notification',
color: '#4285f4',
},
},
apns: {
payload: {
aps: {
badge: 1,
sound: 'default',
},
},
},
});
}
// Send to topic
async function sendToTopic(topic: string) {
await admin.messaging().send({
topic,
notification: {
title: 'Breaking News',
body: 'Something important happened',
},
});
}
// Send to multiple devices
async function sendToMultiple(tokens: string[]) {
await admin.messaging().sendEachForMulticast({
tokens,
notification: {
title: 'Update',
body: 'New features available',
},
});
}curl -X POST \
'https://fcm.googleapis.com/v1/projects/YOUR_PROJECT/messages:send' \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"message": {
"token": "DEVICE_TOKEN",
"notification": {
"title": "Hello",
"body": "World"
}
}
}'import { PushNotifications } from '@capacitor/push-notifications';
// Create channel
await PushNotifications.createChannel({
id: 'messages',
name: 'Messages',
description: 'Message notifications',
importance: 5, // Max importance
visibility: 1, // Public
sound: 'notification.wav',
vibration: true,
lights: true,
lightColor: '#FF0000',
});
// Delete channel
await PushNotifications.deleteChannel({ id: 'old-channel' });
// List channels
const channels = await PushNotifications.listChannels();// Subscribe to topic
await PushNotifications.addListener('registration', async () => {
// Subscribe to topics based on user preferences
const messaging = getMessaging();
await subscribeToTopic(messaging, 'news');
await subscribeToTopic(messaging, 'promotions');
});// ios/App/NotificationService/NotificationService.swift
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
contentHandler(request.content)
return
}
// Add image
if let imageUrl = request.content.userInfo["image"] as? String,
let url = URL(string: imageUrl) {
downloadImage(url: url) { attachment in
if let attachment = attachment {
mutableContent.attachments = [attachment]
}
contentHandler(mutableContent)
}
} else {
contentHandler(mutableContent)
}
}
}// Handle action buttons
PushNotifications.addListener('pushNotificationActionPerformed', (action) => {
switch (action.actionId) {
case 'reply':
// Handle reply action
const input = action.inputValue;
sendReply(input);
break;
case 'dismiss':
// Handle dismiss
break;
default:
// Handle tap
navigateToContent(action.notification.data);
}
});// Server-side: Send data-only message
{
"to": "DEVICE_TOKEN",
"data": {
"type": "sync",
"action": "refresh"
}
// No "notification" key = data-only
}// android/app/src/main/java/.../FirebaseService.kt
class FirebaseService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
// Handle data message in background
message.data["type"]?.let { type ->
when (type) {
"sync" -> performBackgroundSync()
"update" -> checkForUpdates()
}
}
}
}import { LocalNotifications } from '@capacitor/local-notifications';
// Show local notification when in foreground
PushNotifications.addListener('pushNotificationReceived', async (notification) => {
await LocalNotifications.schedule({
notifications: [
{
id: Date.now(),
title: notification.title || '',
body: notification.body || '',
extra: notification.data,
},
],
});
});async function requestNotificationPermission() {
const { receive } = await PushNotifications.checkPermissions();
if (receive === 'prompt') {
// Show explanation first
const shouldRequest = await showPermissionExplanation();
if (shouldRequest) {
const result = await PushNotifications.requestPermissions();
return result.receive === 'granted';
}
return false;
}
if (receive === 'denied') {
// Guide user to settings
showSettingsPrompt();
return false;
}
return receive === 'granted';
}// Handle token refresh
PushNotifications.addListener('registration', async (token) => {
const oldToken = await getStoredToken();
if (oldToken !== token.value) {
// Token changed, update server
await updateServerToken(oldToken, token.value);
await storeToken(token.value);
}
});PushNotifications.addListener('registrationError', (error) => {
console.error('Push registration failed:', error);
// Log to analytics
analytics.logEvent('push_registration_failed', {
error: error.error,
});
// Retry with backoff
scheduleRetry();
});| Issue | Solution |
|---|---|
| No token | Check permissions, network |
| Foreground only | Implement background handler |
| Delayed delivery | Use high priority, data-only |
| No sound | Configure notification channel |
| Badge not updating | Set badge in payload |