基于Cloud Functions和Cloud Messaging实现Firestore新增文档的Flutter推送通知
问题解决与需求实现指南
一、先解决Cloud Functions部署与触发报错问题
1. 修复Admin SDK初始化代码
在Cloud Functions云端环境中,Firebase会自动为Admin SDK提供默认凭据,不需要手动加载serviceAccountKey.json,手动配置会导致部署冲突。修改函数代码如下:
import * as functions from "firebase-functions"; import * as admin from "firebase-admin"; // 直接初始化,无需额外参数 admin.initializeApp(); exports.pushNotifications = functions .region('europe-central2') .firestore.document("signals/{docId}").onCreate(async (snapshot) => { // 后续逻辑编写 });
2. 解决权限拒绝问题
部署后出现access denied是因为Cloud Functions默认服务账号缺少必要权限,需在Firebase控制台IAM页面配置:
- 找到项目默认服务账号:
[项目ID]@appspot.gserviceaccount.com - 为其添加Firebase Cloud Messaging Admin角色(用于发送推送)
- 同时确保该账号拥有Cloud Firestore Viewer角色(用于读取signals文档内容)
二、实现三个核心需求
1. 全状态通知显示(前台/后台/停止)
后台状态已正常,前台需手动监听消息并展示本地通知,先安装flutter_local_notifications包,补充Flutter端代码:
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin(); // 初始化本地通知(app启动时调用) Future<void> initLocalNotifications() async { const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const InitializationSettings initSettings = InitializationSettings(android: androidSettings); await _localNotifications.initialize(initSettings); } // 监听前台消息并展示通知 FirebaseMessaging.onMessage.listen((RemoteMessage message) { RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android; if (notification != null && android != null) { _localNotifications.show( notification.hashCode, notification.title, notification.body, NotificationDetails( android: AndroidNotificationDetails( 'signal_notification_channel', // 自定义渠道ID 'Signal Notifications', // 渠道名称 importance: Importance.max, priority: Priority.high, ), ), payload: message.data['signalId'], // 传递信号ID ); } }); // 处理后台/前台通知点击跳转 FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { String signalId = message.data['signalId'] ?? ''; // 跳转到信号详情页,示例: // Navigator.push(context, MaterialPageRoute(builder: (context) => SignalDetailPage(id: signalId))); }); // 处理应用未启动时的通知点击 Future<void> handleInitialMessage() async { RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initialMessage != null) { String signalId = initialMessage.data['signalId'] ?? ''; // 跳转到详情页 } }
调用顺序:app初始化时先执行initLocalNotifications()和handleInitialMessage(),再配置Firebase Messaging。
2. 定向推送(按区域发送)
第一步:存储用户订阅信息
在Firestore创建user_subscriptions集合,每个文档结构如下:
{ "userId": "用户Firebase UID", "fcmToken": "用户设备FCM token", "watchedRegions": ["区域A", "区域B"] // 用户关注的区域 }
Flutter端在用户设置关注区域时更新该文档,注意定期更新token(token可能会变化)。
第二步:修改Cloud Functions逻辑
新signal创建时,读取对应区域的订阅token并批量发送:
exports.pushNotifications = functions .region('europe-central2') .firestore.document("signals/{docId}").onCreate(async (snapshot) => { const signalData = snapshot.data(); const signalRegion = signalData.region; // 假设signal文档包含region字段 const signalId = snapshot.id; // 查询关注该区域的用户token const subSnapshot = await admin.firestore() .collection('user_subscriptions') .where('watchedRegions', 'array-contains', signalRegion) .get(); const tokens = subSnapshot.docs.map(doc => doc.data().fcmToken).filter(token => token != null); if (tokens.length === 0) { console.log('该区域无订阅用户'); return null; } // 构造推送内容 const payload = { notification: { title: '新信号通知', body: `区域${signalRegion}新增一条信号` }, data: { signalId: signalId, // 传递信号ID region: signalRegion } }; // 批量发送推送 const response = await admin.messaging().sendToDevice(tokens, payload); console.log('推送发送完成', response); return response; });
3. 通知关联信号详情
上述代码已通过data字段传递signalId,Flutter端在onMessageOpenedApp和handleInitialMessage回调中获取该ID,直接跳转至对应信号详情页面即可。
内容的提问来源于stack exchange,提问作者Miro
相关产品推荐
相关产品推荐

