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

Flutter iOS端FCM通知点击未触发getInitialMessage等回调问题

Flutter通知点击无回调问题排查

问题背景

开发Flutter应用时,点击Firebase推送的通知无法触发自定义回调,只会打开应用首页。通知由Node.js后端通过firebase-admin发送,客户端已配置getInitialMessage、onMessageOpenedApp等回调,但均未生效。

后端发送逻辑(Node.js)

/**
 * Trigger sending a notification
 */
const sendNotification = async ({
    title,
    body,
    data,
    notificationType,
    topic,
    sendTo,
    mediaUrl,
}) => {
    let tokens = [];
    if (sendTo) {
        console.log(`sending targeted notitification to: ${sendTo}`);
        tokens = await getUserFCMTokens(sendTo);
    }
    const notification = {
        data: {
            ...data,
            notificationType: NOTIFICATION_TYPE[notificationType].toString(),
            title: title,
            body: body,
        },
        ...(topic && { topic: topic }),
        ...(tokens.length > 0 && { tokens }),
        apns: {
            headers: {
                'apns-priority': '5',
            },
            payload: {
                aps: {
                    contentAvailable: true,
                },
            },
        },
        android: {
            priority: 'high',
            notification: {
                click_action: 'FLUTTER_NOTIFICATION_CLICK',
                priority: 'high',
                sound: 'default',
            },
        },
    };

    console.log(notification);

    try {
        if (tokens.length > 0) {
            // Send to multiple devices
            const response = await admin
                .messaging()
                .sendEachForMulticast(notification);
            console.log(
                `Successfully sent message to ${response.successCount} devices`
            );
            if (response.failureCount > 0) {
                console.log(
                    `Failed to send message to ${response.failureCount} devices`
                );
                response.responses.forEach((resp, idx) => {
                    if (!resp.success) {
                        console.log(
                            `Failed to send to token at index ${idx}: ${resp.error}`
                        );
                    }
                });
            }
        } else if (topic) {
            // Send to topic
            const response = await admin.messaging().send(notification);
            console.log('Successfully sent message:', response);
        } else {
            console.log('No valid recipients (tokens or topic) specified');
        }
    } catch (error) {
        console.error('Error sending notification:', error);
    }
};

通知结构示例

{
  data: {
    group: '6716a89768497667e665546c',
    media: '',
    message: '6716a8d868497667e66554a4',
    notificationType: '3',
    title: 'title goes here',
    body: '@zh22: Message goes here '
  },
  topic: 'group_6716a89768497667e665546c',
  apns: { headers: { 'apns-priority': '5' }, payload: { aps: [Object] } },
  android: {
    priority: 'high',
    notification: {
      click_action: 'FLUTTER_NOTIFICATION_CLICK',
      priority: 'high',
      sound: 'default'
    }
  }
}

客户端配置(Flutter main.dart)

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  await AppData.initiate();

  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    print("onMessage: $message");
  });

  FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
    print("onMessageOpenedApp: $message");
    NotificationHandler.handleNotificationOnTap(message);
  });

  FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage);

  FirebaseMessaging.instance
      .getInitialMessage()
      .then(NotificationHandler.handleNotificationOnTap);

  runApp(
    ShowCaseWidget(builder: (context) {
      return const MyApp();
    }),
  );
}

@pragma("vm:entry-point")
Future<void> _onBackgroundMessage(RemoteMessage message) async {
  print("onBackgroundMessage: $message");
  print("data: ${message.data}");

  final NotificationRepository notificationRepository =
      NotificationRepositoryImpl();

  final FlutterLocalNotificationsPlugin localNotificationsPlugin =
      FlutterLocalNotificationsPlugin();
  // show notification
  const AndroidNotificationDetails androidNotificationDetails =
      AndroidNotificationDetails(
          "basecamp_notifications", "Basecamp Notifications",
          importance: Importance.max,
          priority: Priority.high,
          ticker: 'ticker');

  const DarwinNotificationDetails iosNotificationDetails =
      DarwinNotificationDetails(
          presentAlert: true,
          presentSound: true,
          interruptionLevel: InterruptionLevel.active);

  int notificationId = 1;

  const NotificationDetails platformSpecifics = NotificationDetails(
      android: androidNotificationDetails, iOS: iosNotificationDetails);

  await localNotificationsPlugin.initialize(
    const InitializationSettings(
      android: AndroidInitializationSettings("@mipmap/ic_launcher"),
      iOS: DarwinInitializationSettings(),
    ),
    onDidReceiveNotificationResponse: (NotificationResponse details) {
  
      if (details.payload != null) {
        final data = json.decode(details.payload!);
        final message = RemoteMessage(data: Map<String, String>.from(data));
        NotificationHandler.handleNotificationOnTap(message);
      }
    },
  );

    final title = message.data["title"];
    final body = message.data["body"];

    await localNotificationsPlugin.show(
        notificationId, title, body, platformSpecifics,
        payload: message.data.toString());
  
}

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
  print(notificationResponse);
  // NotificationHandler.handleNotificationOnTap();
}

客户端日志输出

flutter: onBackgroundMessage: Instance of 'RemoteMessage'
flutter: data: {body: @zh22: Message goes here, message: 6716a8d868497667e66554a4, title: @title goes here, group: 6716a89768497667e665546c, notificationType: 3, media: }
flutter: remoteMessage: null

关键问题点

  • 点击通知后无任何日志输出,getInitialMessage、onMessageOpenedApp均未触发
  • NotificationHandler.handleNotificationOnTap()从未执行
  • iOS环境已配置Push Notification能力,FirebaseAppDelegateProxyEnabled设为NO

排查方向与解决方案

1. APNS payload配置缺失(iOS核心问题)

当前后端发送的APNS payload仅设置了contentAvailable: true,但没有包含alert字段。对于iOS来说,纯data消息在后台时会触发onBackgroundMessage,但系统不会自动展示通知——你现在是通过本地通知插件手动展示的,但此时点击的是本地通知,而非Firebase系统通知,因此Firebase的onMessageOpenedApp和getInitialMessage不会触发。

修改后端APNS配置,添加alert字段,让系统直接展示通知:

apns: {
    headers: {
        'apns-priority': '5',
    },
    payload: {
        aps: {
            contentAvailable: true,
            alert: { // 新增alert字段
                title: title,
                body: body
            },
            sound: 'default'
        },
    },
},

这样iOS系统会直接展示通知,点击时Firebase的回调才能正常触发。

2. 本地通知初始化时机错误

在_onBackgroundMessage中每次收到消息都重新初始化FlutterLocalNotificationsPlugin,这会覆盖之前的回调配置。应该将初始化移到main函数中,而非后台消息处理函数内:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  // 提前初始化本地通知插件
  final FlutterLocalNotificationsPlugin localNotificationsPlugin =
      FlutterLocalNotificationsPlugin();
  const AndroidNotificationDetails androidNotificationDetails =
      AndroidNotificationDetails(
          "basecamp_notifications", "Basecamp Notifications",
          importance: Importance.max,
          priority: Priority.high,
          ticker: 'ticker');

  const DarwinNotificationDetails iosNotificationDetails =
      DarwinNotificationDetails(
          presentAlert: true,
          presentSound: true,
          interruptionLevel: InterruptionLevel.active);

  const NotificationDetails platformSpecifics = NotificationDetails(
      android: androidNotificationDetails, iOS: iosNotificationDetails);

  await localNotificationsPlugin.initialize(
    const InitializationSettings(
      android: AndroidInitializationSettings("@mipmap/ic_launcher"),
      iOS: DarwinInitializationSettings(),
    ),
    onDidReceiveNotificationResponse: (NotificationResponse details) {
      if (details.payload != null) {
        final data = json.decode(details.payload!);
        final message = RemoteMessage(data: Map<String, String>.from(data));
        NotificationHandler.handleNotificationOnTap(message);
      }
    },
    onDidReceiveBackgroundNotificationResponse: notificationTapBackground // 补充后台点击回调
  );

  await AppData.initiate();

  // ... 其他Firebase回调配置

  runApp(...);
}

3. 确认FirebaseAppDelegateProxyEnabled配置

虽然你已设置为NO,但需要确保AppDelegate中正确处理了通知回调。如果禁用了代理,需要手动在AppDelegate.swift中添加代码:

import UIKit
import Flutter
import FirebaseMessaging

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    FirebaseApp.configure()
    GeneratedPluginRegistrant.register(with: self)
    
    // 注册远程通知
    UNUserNotificationCenter.current().delegate = self
    application.registerForRemoteNotifications()
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  
  // 处理通知点击
  override func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    if let message = Messaging.messaging().appDidReceiveMessage(userInfo) {
      // 将消息传递给Flutter
      NotificationCenter.default.post(name: NSNotification.Name("FCMNotificationTap"), object: message)
    }
    completionHandler()
  }
}

同时在Flutter中监听这个通知,确保点击事件能传递到你的处理逻辑。

4. 检查getInitialMessage的调用时机

确保getInitialMessage在Firebase初始化完成后调用,当前代码位置是正确的,但需要确认NotificationHandler.handleNotificationOnTap中是否正确处理了null情况——日志中出现remoteMessage: null可能是因为该函数接收了null参数,需要添加判断:

static void handleNotificationOnTap(RemoteMessage? message) {
  if (message == null) {
    print("remoteMessage: null");
    return;
  }
  // 处理跳转逻辑
}

内容的提问来源于stack exchange,提问作者zh24

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 21:14:53