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

如何基于通知载荷数据条件控制Flutter iOS通知的显示/隐藏

问题描述

我开发了一款基于Flutter(Firebase)的应用,使用Cloud Function发送通知。希望根据条件控制通知的显示与隐藏:当用户与通知载荷中经纬度的距离在50公里范围内时显示通知,否则隐藏,且该逻辑需在应用前台、后台或被杀死状态下均生效。用户的经纬度从FireStore获取,通知的经纬度来自通知载荷。我已查阅Firebase官方通知文档,但现有案例无法匹配我的需求。以下是我的Cloud Function代码骨架:

var flutterMessage = {
    data: {
        "id": docID,
        "companyId": companyId,
        "userId": userId,
        // 更多数据
        "latitude": JSON.stringify(latitude),
        "longitude": JSON.stringify(longitude),
    },
    notification: {
        title: title,
        body: companyName,
    },
    apns: {
        payload: {
            aps: {
                sound: "default",
            },
        },
    },
    topic: 'New-Doc',
};


let flutterResponse = await admin.messaging().send(flutterMessage).catch((error) => {console.log("Error " + error);});
console.log("response Flutter App" + flutterResponse);

解决方案

要实现全状态下的距离过滤通知,需要Cloud Function端预过滤+Flutter客户端兜底处理结合,单独一端无法覆盖所有场景。

一、Cloud Function端:预筛选符合距离条件的用户

直接给所有订阅主题的用户发通知会浪费资源,先在云函数中计算用户与通知地点的距离,只给符合条件的用户发送:

  1. 从FireStore获取用户集合,读取用户的最新经纬度(假设用户文档存储了lastLatitude、lastLongitude和fcmToken)
  2. 用Haversine公式计算用户位置与通知地点的距离
  3. 收集符合距离条件的用户FCM Token,批量发送通知(替换原有的主题发送方式)

修改后的Cloud Function代码示例:

// 引入Haversine距离计算工具(也可自行实现)
const haversine = require('haversine-distance');

// 通知地点经纬度
const notificationLoc = {
  latitude: parseFloat(latitude),
  longitude: parseFloat(longitude)
};

// 1. 查询所有用户并筛选符合条件的对象
const usersSnapshot = await admin.firestore().collection('users').get();
const eligibleTokens = [];

for (const doc of usersSnapshot.docs) {
  const user = doc.data();
  const userLoc = {
    latitude: user.lastLatitude,
    longitude: user.lastLongitude
  };

  // 2. 计算距离(转成公里)
  const distanceMeters = haversine(notificationLoc, userLoc);
  const distanceKm = distanceMeters / 1000;

  // 3. 筛选距离≤50公里且有FCM Token的用户
  if (distanceKm <= 50 && user.fcmToken) {
    eligibleTokens.push(user.fcmToken);
  }
}

// 4. 批量发送通知给符合条件的用户
if (eligibleTokens.length > 0) {
  const flutterMessage = {
    data: {
      "id": docID,
      "companyId": companyId,
      "userId": userId,
      // 更多数据
      "latitude": JSON.stringify(latitude),
      "longitude": JSON.stringify(longitude),
      "title": title,
      "body": companyName
    },
    apns: {
      payload: {
        aps: {
          sound: "default",
          contentAvailable: true // 允许iOS后台处理数据消息
        },
      },
    },
    tokens: eligibleTokens
  };

  const flutterResponse = await admin.messaging().sendMulticast(flutterMessage)
    .catch(err => console.log("发送错误:" + err));
  console.log("通知发送结果:" + flutterResponse);
}

二、Flutter客户端:全状态兜底处理

用户位置可能在云函数发送通知后更新,或FireStore未同步最新位置,需要在客户端做最终过滤,覆盖所有运行状态:

1. 前台状态处理

通过onMessage监听通知,计算距离后决定是否显示本地通知:

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  // 解析通知中的经纬度
  final notifyLat = double.parse(message.data['latitude']);
  final notifyLng = double.parse(message.data['longitude']);
  
  // 获取用户当前位置(可从本地缓存或实时定位获取)
  final userLat = ...;
  final userLng = ...;

  // 计算距离
  final distance = _calculateHaversine(userLat, userLng, notifyLat, notifyLng);
  
  if (distance <= 50) {
    // 显示本地通知
    _showLocalNotification(message);
  }
});

// Haversine公式计算公里数
double _calculateHaversine(double lat1, double lon1, double lat2, double lon2) {
  const earthRadius = 6371; // 地球半径(公里)
  final dLat = _toRadians(lat2 - lat1);
  final dLon = _toRadians(lon2 - lon1);
  final a = 
    sin(dLat/2) * sin(dLat/2) +
    cos(_toRadians(lat1)) * cos(_toRadians(lat2)) * 
    sin(dLon/2) * sin(dLon/2); 
  final c = 2 * atan2(sqrt(a), sqrt(1-a)); 
  return earthRadius * c;
}

double _toRadians(double degree) => degree * pi / 180;

2. 后台状态处理

配置后台消息处理器,收到通知后先计算距离再决定是否显示:

// 后台消息处理回调
Future<void> _backgroundMessageHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  
  final notifyLat = double.parse(message.data['latitude']);
  final notifyLng = double.parse(message.data['longitude']);
  // 从本地缓存读取用户最近位置
  final userLat = ...;
  final userLng = ...;

  final distance = _calculateHaversine(userLat, userLng, notifyLat, notifyLng);
  
  if (distance <= 50) {
    _showLocalNotification(message);
  }
}

// 初始化时注册后台处理器
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);
  
  runApp(MyApp());
}

3. 应用被杀死状态处理

这种情况下系统会直接展示通知,因此云函数只发送data字段(不包含notification),让应用启动后再处理:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  // 处理应用被杀死后点击通知启动的场景
  final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
  if (initialMessage != null) {
    _handleLaunchMessage(initialMessage);
  }
  
  FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);
  runApp(MyApp());
}

void _handleLaunchMessage(RemoteMessage message) {
  final notifyLat = double.parse(message.data['latitude']);
  final notifyLng = double.parse(message.data['longitude']);
  // 从FireStore或本地缓存获取用户位置
  final userLat = ...;
  final userLng = ...;

  final distance = _calculateHaversine(userLat, userLng, notifyLat, notifyLng);
  
  if (distance <= 50) {
    _showLocalNotification(message);
    // 可选:跳转到对应页面
    Navigator.pushNamed(context, '/notification-detail', arguments: message.data);
  }
}

关键注意事项

  • 用户位置需实时同步到FireStore,同时本地缓存一份,避免云函数和客户端无法获取最新位置。
  • 确保Cloud Function和Flutter端的Haversine计算逻辑一致,避免距离判断偏差。
  • iOS端需开启contentAvailable权限,Android端需配置正确的通知渠道,确保后台消息能被处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 00:05:43