You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:19:59