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

如何用Python在Firebase通知中添加带API跳转的操作按钮?

解决FCM推送带操作按钮通知的问题

问题根源

你之前的修改把notification字段替换成了data字段,导致系统通知栏无法自动弹出通知——FCM的data类型消息需要App自行处理显示逻辑,而notification类型消息才会由系统直接展示在通知栏。同时,操作按钮的配置不符合FCM规范,需要针对Android和iOS分别适配。

一、修正Python端的FCM请求体

保留notification字段确保通知能弹出,同时添加操作按钮配置,并用data字段携带按钮对应的API地址:

import requests
import json

def SendCallNotification():
    try:
        server_token = 'my server token'
        device_token = 'my device token'
    except:
        return False
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'key=' + server_token,
    }
    body = {
        "to": device_token,
        "priority": "high",
        # 通知核心内容,确保系统弹出通知
        "notification": {
            "title": "Incoming Call",
            "body": "You have a new call request",
            "click_action": "FLUTTER_NOTIFICATION_CLICK",
            # Android端操作按钮配置
            "actions": [
                {
                    "action": "RECEIVE_ACTION",
                    "title": "Receive"
                },
                {
                    "action": "DECLINE_ACTION",
                    "title": "Decline"
                }
            ]
        },
        # 携带按钮对应的API地址,供Flutter端调用
        "data": {
            "receive_api": "https://exampleapi.com/api/v1/receive",
            "decline_api": "https://exampleapi.com/api/v1/decline"
        },
        # iOS端需要配置Category,对应后续Flutter的配置
        "apns": {
            "payload": {
                "aps": {
                    "category": "CALL_CATEGORY"
                }
            }
        }
    }
    response = requests.post("https://fcm.googleapis.com/fcm/send", headers=headers, data=json.dumps(body))
    print(response.status_code)
    print(response.json())

SendCallNotification()

二、Flutter端配置与逻辑处理

需要结合firebase_messaging和flutter_local_notifications插件,处理通知显示和按钮点击事件:

1. 依赖配置

在pubspec.yaml中添加依赖:

dependencies:
  firebase_core: ^2.15.0
  firebase_messaging: ^14.6.5
  flutter_local_notifications: ^16.1.0
  http: ^1.1.0

2. 核心逻辑实现

import 'dart:convert';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;

final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await _initializeNotifications();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(appBar: AppBar(title: const Text('FCM Demo'))),
    );
  }
}

Future<void> _initializeNotifications() async {
  // 请求通知权限
  await FirebaseMessaging.instance.requestPermission(
    alert: true,
    badge: true,
    sound: true,
  );

  // 配置Android通知渠道
  const AndroidNotificationChannel androidChannel = AndroidNotificationChannel(
    'call_channel',
    'Call Notifications',
    description: 'Handles incoming call requests',
    importance: Importance.max,
  );
  await _localNotifications.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(androidChannel);

  // 配置iOS通知Category
  const IOSNotificationCategory iosCategory = IOSNotificationCategory(
    'CALL_CATEGORY',
    actions: [
      IOSNotificationAction(
        'RECEIVE_ACTION',
        'Receive',
        options: IOSNotificationActionOptions.foreground,
      ),
      IOSNotificationAction(
        'DECLINE_ACTION',
        'Decline',
        options: IOSNotificationActionOptions.foreground,
      ),
    ],
  );
  await _localNotifications.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()?.setNotificationCategories([iosCategory]);

  // 处理前台收到的通知(FCM前台不会自动弹出,需手动显示)
  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    _showNotification(message);
  });

  // 处理后台/终止状态下的通知点击
  FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationAction);
  RemoteMessage? initialMessage = await FirebaseMessaging.getInitialMessage();
  if (initialMessage != null) {
    _handleNotificationAction(initialMessage);
  }

  // 处理本地通知按钮点击
  _localNotifications.initialize(
    const InitializationSettings(
      android: AndroidInitializationSettings('@mipmap/ic_launcher'),
      iOS: IOSInitializationSettings(),
    ),
    onDidReceiveNotificationResponse: (NotificationResponse response) {
      if (response.payload != null) {
        Map<String, dynamic> data = jsonDecode(response.payload!);
        String action = response.actionId!;
        _callApi(action, data);
      }
    },
  );
}

void _showNotification(RemoteMessage message) {
  const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
    'call_channel',
    'Call Notifications',
    channelDescription: 'Handles incoming call requests',
    actions: [
      AndroidNotificationAction('RECEIVE_ACTION', 'Receive'),
      AndroidNotificationAction('DECLINE_ACTION', 'Decline'),
    ],
  );
  const IOSNotificationDetails iosDetails = IOSNotificationDetails(
    categoryIdentifier: 'CALL_CATEGORY',
  );
  const NotificationDetails notificationDetails = NotificationDetails(
    android: androidDetails,
    iOS: iosDetails,
  );

  _localNotifications.show(
    DateTime.now().millisecond,
    message.notification?.title,
    message.notification?.body,
    notificationDetails,
    payload: jsonEncode(message.data),
  );
}

void _handleNotificationAction(RemoteMessage message) {
  String? action = message.data['action'];
  if (action == null) return;
  _callApi(action, message.data);
}

void _callApi(String action, Map<String, dynamic> data) {
  String url = '';
  if (action == 'RECEIVE_ACTION') {
    url = data['receive_api'] ?? '';
  } else if (action == 'DECLINE_ACTION') {
    url = data['decline_api'] ?? '';
  }
  if (url.isNotEmpty) {
    http.get(Uri.parse(url)).catchError((e) => print('API call failed: $e'));
  }
}

3. AndroidManifest.xml补充配置

在android/app/src/main/AndroidManifest.xml的主Activity标签内添加:

<intent-filter>
    <action android:name="FLUTTER_NOTIFICATION_CLICK" />
    <action android:name="RECEIVE_ACTION" />
    <action android:name="DECLINE_ACTION" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

关键说明

  • 必须保留notification字段才能让系统自动弹出通知,data字段用于携带业务逻辑所需的额外信息。
  • Android和iOS的操作按钮配置逻辑不同:Android在FCM payload的notification.actions中配置,iOS则需要先定义Notification Category,再在FCM payload的apns.payload.aps.category中关联。
  • 前台收到FCM通知时,系统不会自动弹出,需要用flutter_local_notifications手动显示带按钮的本地通知。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:40:00