Push notifications are a powerful tool to engage users — and opening a specific screen within your app from a notification can dramatically enhance the user experience.
Lately, I have been working with FCM and nativePHP. My use case was straightforward: to notify a callee of an incoming call via push notifications. Upon pressing the notification, a deep link to the callee interface was required.
For the record, I have zero experience with mobile app development, so bear with me.
What do I wanna achieve
NativePHP supports deep linking. Deeplinks. This works great when sending for example an email and use your deeplink inside of an email. But here are 2 scenario’s
- Receiving a APN from FCM is not opening a deeplink, tapping it will open the app on the main page.
- When the app is in the foreground APN are not shown send from FCM.
TLDR
What did I change to make this work
First of all we need to create a payload key that has the deeplink value
{
"token": "fbC1hM1erEHmhm8PzeRKkp:APA91bGUlro5G91OY",
"title" => 'Deep link test FCM',
"body" => 'Opens a deeplink in app for the callee',
"data": {
"deeplink": "mysuperapp://asterisk/agent-endpoint"
},
"androidConfig": {
"priority": "high",
"notification": {
"sound": "default"
}
},
"apnsConfig": {
"headers": {
"apns-priority": "10"
},
"payload": {
"aps": {
"sound": "incoming-call.caf"
}
}
}
}
The next part is adding some extra code to the nativephp/ios/AppDelegate.swift file
import SwiftUI
import AVFoundation
import UserNotifications
import FirebaseMessaging
class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
static let shared = AppDelegate()
// Called when the app is launched
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// Set self as the delegate for the Messaging instance
if FirebaseManager.shared.isConfigured {
Messaging.messaging().delegate = self
}
// Check if the app was launched from a URL (custom scheme)
if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
DebugLogger.shared.log("📱 AppDelegate: Cold start with custom scheme URL: \(url)")
// Pass the URL to the DeepLinkRouter
DeepLinkRouter.shared.handle(url: url)
}
// Check if the app was launched from a Universal Link
if let userActivityDictionary = launchOptions?[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [String: Any],
let userActivity = userActivityDictionary["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
DebugLogger.shared.log("📱 AppDelegate: Cold start with Universal Link: \(url)")
// Pass the URL to the DeepLinkRouter
DeepLinkRouter.shared.handle(url: url)
}
// <-- ADDED: Set notification center delegate to self
UNUserNotificationCenter.current().delegate = self
return true
}
// Called for Universal Links
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// Check if this is a Universal Link
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
// Pass the URL to the DeepLinkRouter
DeepLinkRouter.shared.handle(url: url)
return true
}
return false
}
// Called when the user grants (or revokes) notification permissions
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
// Put the token in PHP's memory so the developer can fetch it manually
let tokenString = deviceToken.map { String(format: "%02x", $0) }.joined()
NativePHPSetPushTokenC(tokenString)
if FirebaseManager.shared.isConfigured {
// Pass to Firebase
Messaging.messaging().deleteToken { error in
Messaging.messaging().apnsToken = deviceToken
}
} else {
// Fire TokenGenerated event
LaravelBridge.shared.send?(
"Native\\Mobile\\Events\\PushNotification\\TokenGenerated",
["token": tokenString]
)
}
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register for remote notifications:", error.localizedDescription)
}
// Handle deeplinks
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
// Pass the URL to the DeepLinkRouter
DeepLinkRouter.shared.handle(url: url)
return true
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let fcmToken = fcmToken else { return }
LaravelBridge.shared.send?(
"Native\\Mobile\\Events\\PushNotification\\TokenGenerated",
["token": fcmToken]
)
}
// <-- ADDED: Handle notification tap and extract deeplink URL from payload
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let deeplinkString = userInfo["deeplink"] as? String,
let deeplinkURL = URL(string: deeplinkString) {
DeepLinkRouter.shared.handle(url: deeplinkURL)
}
completionHandler()
}
// <-- ADDED: Show notifications even when app is in foreground (optional)
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.banner, .sound]) //
}
}