Push Notifications Complete Guide
Introduction
Push notifications are essential for mobile app engagement. This guide covers implementation of Firebase Cloud Messaging (FCM) for Android, Apple Push Notification service (APNs) for iOS, and cross-platform solutions.
1. Firebase Cloud Messaging Setup
// React Native - Install dependencies
npm install @react-native-firebase/app @react-native-firebase/messaging
// Android - google-services.json in android/app/
// iOS - GoogleService-Info.plist in ios/
// Request permission
import messaging from '@react-native-firebase/messaging';
async function requestUserPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
const token = await messaging().getToken();
console.log('FCM Token:', token);
// Send token to your server
}
}
// Get FCM token
const getFCMToken = async () => {
try {
const token = await messaging().getToken();
await saveTokenToServer(token);
return token;
} catch (error) {
console.error('Error getting FCM token:', error);
}
};
2. Handle Foreground Notifications
// React Native - Foreground handler
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('Foreground notification:', remoteMessage);
// Show local notification
Alert.alert(
remoteMessage.notification?.title || 'New Notification',
remoteMessage.notification?.body || ''
);
});
return unsubscribe;
}, []);
// Android - Handle foreground notifications
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
remoteMessage.notification?.let {
showNotification(it.title, it.body)
}
remoteMessage.data.isNotEmpty().let {
handleDataPayload(remoteMessage.data)
}
}
private fun showNotification(title: String?, body: String?) {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.build()
NotificationManagerCompat.from(this)
.notify(NOTIFICATION_ID, notification)
}
}
// iOS - Handle foreground notifications
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.banner, .sound, .badge])
}
3. Handle Background Notifications
// React Native - Background handler
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Background notification:', remoteMessage);
// Process notification data
if (remoteMessage.data) {
await processNotificationData(remoteMessage.data);
}
});
// Handle notification opened app
messaging().onNotificationOpenedApp(remoteMessage => {
console.log('Notification opened app:', remoteMessage);
// Navigate to specific screen
if (remoteMessage.data?.screen) {
navigation.navigate(remoteMessage.data.screen);
}
});
// Check initial notification (app opened from quit state)
messaging()
.getInitialNotification()
.then(remoteMessage => {
if (remoteMessage) {
console.log('Notification caused app to open:', remoteMessage);
}
});
// Android - Handle background clicks
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="NOTIFICATION_CLICKED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent?.action == "NOTIFICATION_CLICKED") {
val screen = intent.getStringExtra("screen")
// Navigate to screen
}
}
4. iOS APNs Configuration
// Swift - Request authorization
import UserNotifications
func requestNotificationAuthorization() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
// Register for remote notifications
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("APNs Token:", token)
// Send to server
}
// Handle notification received
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if let aps = userInfo["aps"] as? [String: Any] {
if let alert = aps["alert"] as? String {
print("Notification:", alert)
}
}
completionHandler(.newData)
}
5. Notification Channels (Android 8+)
// Create notification channels
class NotificationHelper(private val context: Context) {
companion object {
const val CHANNEL_GENERAL = "general"
const val CHANNEL_PROMOTIONS = "promotions"
const val CHANNEL_ALERTS = "alerts"
}
fun createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channels = listOf(
NotificationChannel(
CHANNEL_GENERAL,
"General Notifications",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "General app notifications"
},
NotificationChannel(
CHANNEL_PROMOTIONS,
"Promotions",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Promotional offers and updates"
},
NotificationChannel(
CHANNEL_ALERTS,
"Important Alerts",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Critical alerts"
enableVibration(true)
enableLights(true)
}
)
val manager = context.getSystemService(NotificationManager::class.java)
channels.forEach { manager.createNotificationChannel(it) }
}
}
}
// Use specific channel
val notification = NotificationCompat.Builder(context, CHANNEL_ALERTS)
.setContentTitle("Important")
.setContentText("This is an important alert")
.setSmallIcon(R.drawable.ic_alert)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
6. Rich Notifications
// Android - Rich notification with image
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("New Message")
.setContentText("You have a new message")
.setSmallIcon(R.drawable.ic_message)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.profile))
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(BitmapFactory.decodeResource(resources, R.drawable.big_image))
.bigLargeIcon(null))
.build()
// iOS - Rich notifications with media
// Create Notification Service Extension
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let urlString = request.content.userInfo["image_url"] as? String,
let fileUrl = URL(string: urlString) {
downloadImage(fileUrl: fileUrl) { attachment in
if let attachment = attachment {
bestAttemptContent?.attachments = [attachment]
}
contentHandler(bestAttemptContent ?? request.content)
}
}
}
// Action buttons
// Android
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("New Message")
.setContentText("Reply to this message?")
.addAction(R.drawable.ic_reply, "Reply", pendingIntent)
.addAction(R.drawable.ic_dismiss, "Dismiss", dismissIntent)
.build()
// iOS
let replyAction = UNNotificationAction(
identifier: "REPLY_ACTION",
title: "Reply",
options: [.foreground]
)
let category = UNNotificationCategory(
identifier: "MESSAGE_CATEGORY",
actions: [replyAction],
intentIdentifiers: []
)
UNUserNotificationCenter.current().setNotificationCategories([category])
7. Topic Messaging
// Subscribe to topics
await messaging().subscribeToTopic('news');
await messaging().subscribeToTopic('sports');
// Unsubscribe from topics
await messaging().unsubscribeFromTopic('sports');
// Send to topic from server
const message = {
notification: {
title: 'Breaking News',
body: 'Important update available'
},
topic: 'news'
};
await admin.messaging().send(message);
// Conditional topic targeting
const message = {
notification: {
title: 'Sports Update',
body: 'Your team won!'
},
condition: "'sports' in topics && 'soccer' in topics"
};
await admin.messaging().send(message);
8. Server-Side Implementation
// Node.js - Send notification with FCM
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
async function sendNotification(token, title, body, data = {}) {
const message = {
token: token,
notification: {
title: title,
body: body
},
data: data,
android: {
priority: 'high',
notification: {
sound: 'default',
channelId: 'default'
}
},
apns: {
payload: {
aps: {
sound: 'default',
badge: 1
}
}
}
};
try {
const response = await admin.messaging().send(message);
console.log('Successfully sent:', response);
return response;
} catch (error) {
console.error('Error sending notification:', error);
throw error;
}
}
// Send to multiple devices
async function sendToMultipleDevices(tokens, title, body) {
const message = {
notification: { title, body },
tokens: tokens // Array of tokens
};
const response = await admin.messaging().sendMulticast(message);
console.log(`${response.successCount} messages sent successfully`);
// Handle failed tokens
if (response.failureCount > 0) {
response.responses.forEach((resp, idx) => {
if (!resp.success) {
console.error(`Failed token: ${tokens[idx]}`);
// Remove invalid token from database
}
});
}
}
9. Local Notifications
// React Native - Schedule local notification
import notifee from '@notifee/react-native';
async function scheduleLocalNotification() {
// Create channel
const channelId = await notifee.createChannel({
id: 'default',
name: 'Default Channel',
});
// Schedule notification
await notifee.displayNotification({
title: 'Reminder',
body: 'Time to check your app!',
android: {
channelId,
smallIcon: 'ic_launcher',
pressAction: {
id: 'default',
},
},
});
}
// Android - Local notification
fun showLocalNotification(context: Context, title: String, message: String) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.build()
notificationManager.notify(Random.nextInt(), notification)
}
// iOS - Local notification
let content = UNMutableNotificationContent()
content.title = "Reminder"
content.body = "Time to check your app!"
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error scheduling notification:", error)
}
}
10. Best Practices
// Token refresh handling
messaging().onTokenRefresh(async token => {
console.log('New FCM token:', token);
await updateTokenOnServer(token);
});
// Badge management
// iOS
UIApplication.shared.applicationIconBadgeNumber = 0
// Android
NotificationManagerCompat.from(context).cancelAll()
// Notification analytics
const trackNotificationOpened = (notificationId, campaignId) => {
analytics().logEvent('notification_opened', {
notification_id: notificationId,
campaign_id: campaignId,
timestamp: Date.now()
});
};
// Notification preferences
interface NotificationPreferences {
enabled: boolean;
categories: {
news: boolean;
promotions: boolean;
updates: boolean;
};
quietHours: {
enabled: boolean;
start: string;
end: string;
};
}
async function updateNotificationPreferences(prefs: NotificationPreferences) {
if (!prefs.enabled) {
await messaging().deleteToken();
} else {
// Subscribe/unsubscribe from topics
for (const [category, enabled] of Object.entries(prefs.categories)) {
if (enabled) {
await messaging().subscribeToTopic(category);
} else {
await messaging().unsubscribeFromTopic(category);
}
}
}
}
💡 Best Practices:
- Request permission at the right time with context
- Handle token refresh properly
- Use notification channels for Android
- Implement deep linking for notification actions
- Respect user preferences and quiet hours
- Track notification analytics
- Test on multiple devices and OS versions
- Provide value in every notification
- Don't spam users with too many notifications
- Handle failed tokens and clean up database
Conclusion
Push notifications are powerful engagement tools when implemented correctly. Follow these patterns to create effective notification strategies that provide value to users and drive app engagement.