Push Notifications: iOS and Android Implementation Guide
Push notifications are messages sent from a server to a mobile device that appear outside the app — using Apple Push Notification service (APNs) for iOS and Firebase Cloud Messaging (FCM) for Android to deliver time-sensitive updates, alerts, and data payloads.
What You’ll Learn
You’ll implement push notifications with APNs and FCM, manage device tokens, design notification payloads, handle foreground and background delivery, create notification channels, build rich notifications with images and actions, and implement deep linking from notifications.
Why Push Notifications Matter
Push notifications are the primary channel for re-engaging users. Doda Browser uses notifications to alert users about downloads completing. Durga Antivirus Pro sends real-time threat alerts. Push notifications increase retention by up to 3x — users who enable notifications return to the app significantly more often than those who don’t.
Push Notifications Learning Path
flowchart LR
A[Mobile Development Overview] --> B[Android / iOS Development]
B --> C[Mobile Security]
C --> D[Push Notifications]
D --> E[App Store Deployment]
D:::current
classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
How Push Notifications Work
flowchart LR
App[Your App] -->|1. Register for notifications| OS[OS Push Service]
OS -->|2. Returns device token| App
App -->|3. Send token to your server| Server[Your Backend]
Server -->|4. Send notification request| PushService[APNs / FCM]
PushService -->|5. Deliver notification| Device[User Device]
Device -->|6. App handles tap| App
FCM (Firebase Cloud Messaging) — Android
Step 1: Add Firebase to your project
// build.gradle.kts (project level)
plugins {
id("com.google.gms.google-services") version "4.4.1" apply false
}
// build.gradle.kts (app level)
plugins {
id("com.google.gms.google-services")
}
dependencies {
implementation("com.google.firebase:firebase-messaging:24.0.0")
}Step 2: Create a service to handle messages
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class DodaTechMessagingService : FirebaseMessagingService() {
// Called when a new token is generated
override fun onNewToken(token: String) {
super.onNewToken(token)
// Send token to your backend server
sendTokenToServer(token)
}
// Called when a message is received
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// Check message type
message.notification?.let {
// Display notification
showNotification(it.title ?: "", it.body ?: "")
}
message.data.let { data ->
// Handle data payload (silent notification)
handleDataPayload(data)
}
}
private fun showNotification(title: String, body: String) {
val channelId = "general_notifications"
val notificationManager = getSystemService(NotificationManager::class.java)
// Create channel (Android 8+)
val channel = NotificationChannel(
channelId,
"General Notifications",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
// Build notification
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
}
}Step 3: Request permission (Android 13+)
// In your Activity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
NOTIFICATION_PERMISSION_CODE
)
}
}
// Get device token
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
Log.d("FCM", "Token: $token")
// Send to server
}
}Step 4: Send a notification from your server
import requests
def send_fcm_notification(token, title, body):
url = "https://fcm.googleapis.com/fcm/send"
headers = {
"Authorization": "key=YOUR_SERVER_KEY",
"Content-Type": "application/json"
}
payload = {
"to": token,
"notification": {
"title": title,
"body": body,
"sound": "default"
},
"data": {
"type": "new_message",
"conversation_id": "123"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())Expected response:
{"multicast_id": 123456789, "success": 1, "failure": 0, "canonical_ids": 0}APNs (Apple Push Notification Service) — iOS
Step 1: Enable Push Notifications in Xcode
- Go to Target > Signing & Capabilities > + Capability > Push Notifications
- Enable Remote notifications in Background Modes
Step 2: Register for notifications
import UIKit
import UserNotifications
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Request permission
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}
}
return true
}
// Called when device token is received
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { String(format: "%02.2hhx", $0) }
let token = tokenParts.joined()
print("APNs Token: \(token)")
// Send to your backend server
sendTokenToServer(token)
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
}Step 3: Handle incoming notifications
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
// Called when notification is delivered while app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
print("Received notification: \(userInfo)")
// Show banner even when app is in foreground
completionHandler([.banner, .sound, .badge])
}
// Called when user taps on notification
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
handleNotificationTap(userInfo)
completionHandler()
}
private func handleNotificationTap(_ userInfo: [AnyHashable: Any]) {
guard let type = userInfo["type"] as? String else { return }
switch type {
case "new_message":
// Navigate to conversation screen
navigateToConversation(id: userInfo["conversation_id"] as? String ?? "")
case "alert":
// Navigate to alert details
navigateToAlert(id: userInfo["alert_id"] as? String ?? "")
default:
break
}
}
}Notification Payloads
FCM Payload
{
"to": "device_token_here",
"notification": {
"title": "New Message",
"body": "Alice: Are you free tomorrow?",
"image": "https://example.com/image.jpg"
},
"data": {
"type": "new_message",
"conversation_id": "conv_123",
"sender_id": "user_456"
},
"android": {
"priority": "high",
"notification": {
"channel_id": "messages",
"click_action": "OPEN_CONVERSATION"
}
}
}APNs Payload
{
"aps": {
"alert": {
"title": "New Message",
"body": "Alice: Are you free tomorrow?"
},
"sound": "default",
"badge": 5,
"category": "message"
},
"type": "new_message",
"conversation_id": "conv_123"
}Rich Notifications
Notifications with images, actions, and custom UI.
Android: Notification with image
val bigPictureStyle = NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.setSummaryText("Tap to view photo")
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Photo received")
.setContentText("Alice sent you a photo")
.setStyle(bigPictureStyle)
.addAction(R.drawable.ic_reply, "Reply", replyPendingIntent)
.build()iOS: Notification with image and actions
// Register notification actions
let replyAction = UNNotificationAction(identifier: "REPLY", title: "Reply", options: .authenticationRequired)
let dismissAction = UNNotificationAction(identifier: "DISMISS", title: "Dismiss", options: .destructive)
let category = UNNotificationCategory(identifier: "message", actions: [replyAction, dismissAction], intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([category])Deep Linking from Notifications
Deep linking navigates users to specific content when they tap a notification.
Android Deep Linking
<!-- AndroidManifest.xml -->
<activity android:name=".ConversationActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="dodatech" android:host="conversation" />
</intent-filter>
</activity>// Navigate to deep link
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("dodatech://conversation/conv_123"))
startActivity(intent)iOS Deep Linking
// AppDelegate.swift — Handle universal link
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
handleDeepLink(url: url)
return true
}Common Push Notification Errors
1. Missing or Invalid Device Token
If the device token is nil or changes unexpectedly, notifications won’t deliver. Always send the latest token to your server, and handle token refresh callbacks.
2. Not Requesting Permission Properly
iOS and Android 13+ require explicit permission. If you don’t request it, notifications are blocked silently. Check permission status before sending.
3. Foreground Notifications Not Showing
On both platforms, notifications don’t display automatically when the app is in the foreground. You must handle this in code (see willPresent delegate on iOS, onMessageReceived on Android).
4. Payload Size Limits
APNs limit payloads to 4KB. FCM allows up to 4KB for notifications and 2KB for data payloads. Keep payloads small.
5. Not Handling Notification Tapping
If a user taps a notification and nothing happens, they’ll uninstall. Always implement the tap handler to navigate to relevant content.
6. Sending Notifications to Stale Tokens
When a user uninstalls and reinstalls, their token changes. FCM returns NotRegistered error for stale tokens. Remove them from your database.
7. Notification Channel Mismatch (Android)
If you send a notification with a channel ID that doesn’t exist, the notification is dropped. Create all channels before the first notification.
Practice Questions
1. What’s the difference between FCM and APNs?
FCM (Firebase) is Google’s cross-platform push service for Android and iOS. APNs is Apple’s native service for iOS. FCM can deliver to both platforms, while APNs only works on Apple devices.
2. How do you handle a notification when the app is in the foreground?
On iOS, implement userNotificationCenter(_:willPresent:) and specify presentation options. On Android, handle it in onMessageReceived() and build a notification.
3. What is a device token and why does it change?
A device token is a unique identifier the push service assigns to your app on a specific device. It changes when the user reinstalls the app, restores from backup, or on iOS when they reset their device.
4. How do you implement deep linking from a notification?
Include a data payload with navigation parameters. When the user taps the notification, read the data payload and navigate to the appropriate screen using deep links or intent extras.
5. Challenge: Build a notification system for a chat app.
Design a push notification system that: shows the message preview, includes a “Reply” action button, groups notifications by conversation, and deep links to the specific conversation on tap. Implement it for both iOS and Android.
FAQ
Try It Yourself
Implement push notifications in a test app:
- Set up FCM for Android (or APNs for iOS)
- Request notification permission
- Get the device token and log it
- Send a test notification from Firebase Console
- Handle the notification tap to navigate to a specific screen
- Add a notification channel/settings screen
What’s Next
Push notifications are one of the most effective user engagement tools. Start with a simple notification for a key user action (like “Download complete” in DodaZIP), then expand into rich notifications with images, actions, and deep linking as your notification strategy matures.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro