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]) // } }