Flutter中iOS点击通知时如何获取APN推送Payload
iOS推送通知Payload参数提取问题
问题背景
应用架构:
- 后端使用
node-apn包直接向iOS设备发送推送通知 - 通过Firebase获取APN令牌
Android端使用FCM发送通知功能正常,但iOS端通过node-apn发送通知时,点击通知仅能打开应用首页,无法提取Payload中的自定义参数(需根据参数跳转至指定页面)。
相关代码
后端Node.js代码
var apnProvider = new apn.Provider(apnOptions); var note = new apn.Notification(); // 设置通知内容 … apnProvider .send(note, token obtained from FirebaseMessaging.instance.getAPNSToken())
最终构造的通知内容
{ encoding: 'utf8', payload: { messageFrom: 'Health', params: { title: 'Health', body: 'Hello, You have a request. Kindly check.', notification_type: 18, appointment_id: "610cc37686000662ad14c", send_time_in_ms: 1736510662242 } }, compiled: false, expiry: 1736514262, priority: 10, topic: 'mytopic', // 注意修正为英文引号 pushType: 'alert' }
Flutter项目AppDelegate.swift代码
@main @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // 为action isolate提供通信支持 FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in GeneratedPluginRegistrant.register(with: registry) } // 配置本地通知代理 if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } GeneratedPluginRegistrant.register(with: self) // 配置VOIP推送 let mainQueue = DispatchQueue.main let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue) voipRegistry.delegate = self voipRegistry.desiredPushTypes = [PKPushType.voIP] print("didFinishLaunchingWithOptions") return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // VOIP推送处理 func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { print(credentials.token) let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined() print(deviceToken) // 将VOIP令牌保存到服务器 SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken) } func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { print("didInvalidatePushTokenFor") SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("") } }
解决方案
1. 修正后端通知格式
先修正通知内容中的topic字段引号(原示例为中文引号,需改为英文引号'mytopic'),避免推送失败或解析异常。
2. iOS端补充通知回调逻辑
当前AppDelegate仅设置了通知代理,但未实现回调方法来提取Payload参数。需按以下步骤修改:
步骤1:明确遵守UNUserNotificationCenterDelegate协议
修改类定义,添加协议:
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate {
步骤2:添加前台通知接收回调(可选)
用于应用在前台时接收通知并处理参数:
@available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { let userInfo = notification.request.content.userInfo if let params = userInfo["params"] as? [String: Any] { print("前台收到通知参数: \(params)") // 可通过MethodChannel传递给Flutter } // 允许通知显示 completionHandler([.banner, .sound]) }
步骤3:添加通知点击回调
处理用户点击通知(应用在后台或杀死状态)的场景:
@available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo if let params = userInfo["params"] as? [String: Any] { print("点击通知获取参数: \(params)") // 通过MethodChannel将参数传递给Flutter页面 let channel = FlutterMethodChannel(name: "notification_channel", binaryMessenger: self.window?.rootViewController as! FlutterBinaryMessenger) channel.invokeMethod("handleNotificationTap", arguments: params) } completionHandler() }
步骤4:处理从通知启动应用的场景
在didFinishLaunchingWithOptions中添加逻辑,处理应用从杀死状态通过通知启动的情况:
override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // ... 原有代码 ... // 处理从通知启动应用 if let notification = launchOptions?[.remoteNotification] as? [String: Any] { if let params = notification["params"] as? [String: Any] { print("从通知启动获取参数: \(params)") let channel = FlutterMethodChannel(name: "notification_channel", binaryMessenger: self.window?.rootViewController as! FlutterBinaryMessenger) channel.invokeMethod("handleNotificationLaunch", arguments: params) } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) }
3. Flutter侧接收参数并跳转页面
在Flutter中创建MethodChannel接收iOS传递的参数,实现页面跳转:
import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; class NotificationHandler extends StatefulWidget { const NotificationHandler({super.key}); @override State<NotificationHandler> createState() => _NotificationHandlerState(); } class _NotificationHandlerState extends State<NotificationHandler> { static const MethodChannel _channel = MethodChannel('notification_channel'); @override void initState() { super.initState(); _setupNotificationListener(); } void _setupNotificationListener() { _channel.setMethodCallHandler((call) async { Map<String, dynamic> params = Map<String, dynamic>.from(call.arguments); switch (call.method) { case 'handleNotificationTap': case 'handleNotificationLaunch': _navigateToTargetPage(params); break; } }); } void _navigateToTargetPage(Map<String, dynamic> params) { // 根据notification_type和appointment_id跳转对应页面 if (params['notification_type'] == 18) { Navigator.push( context, MaterialPageRoute( builder: (context) => AppointmentDetailPage( appointmentId: params['appointment_id'] as String, ), ), ); } // 其他通知类型的跳转逻辑 } @override Widget build(BuildContext context) { return const Scaffold( body: Center(child: Text('首页')), ); } } // 示例详情页 class AppointmentDetailPage extends StatelessWidget { final String appointmentId; const AppointmentDetailPage({super.key, required this.appointmentId}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('预约详情')), body: Center(child: Text('预约ID: $appointmentId')), ); } }
内容的提问来源于stack exchange,提问作者Djai
相关产品推荐
相关产品推荐

