【问题标题】:Where should put the condition to send push notification in flutter?发送推送通知的条件应该放在哪里?
【发布时间】:2021-12-31 19:57:22
【问题描述】:

我想构建一个应用程序,例如当我将带有纬度和经度信息的新数据保存到 Firebase 时,然后在我的应用程序中计算这些纬度和经度与用户当前位置之间的距离。如果距离小于 60 公里,则发送onBackgroundMessage 通知。我不会将用户的当前位置存储在 firebase 上。我使用函数_getCurrentLocation 获取用户的当前位置。 问题是我不知道在哪里以及如何放置isValidDistance 来检查距离是否低于 60 公里。 目前我的应用会发送通知,但不是按距离发送通知。

index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();

exports.myFunction = functions.firestore
    .document("animal/{message}")
    .onCreate((snapshot, context) => {
      return admin.messaging().sendToTopic("animal", {
        data: {
          latitude: snapshot.data()["latitude"].toString(),
          longitude: snapshot.data()["longitude"].toString(),
        },
        notification: {
          title: snapshot.data().username,
          body: snapshot.data().description,
          clickAction: "FLUTTER_NOTIFICATION_CLICK",
        },
      });
    });

main.dart

Future<void> _messageHandler(RemoteMessage message) async {
  print('background message ${message.data}');
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  double? currentLatitude;
  double? currentLongitude;

  Future<void> _getCurrentLocation() async {
    final locData = await Location().getLocation();
    setState(() {
      currentLatitude = locData.latitude;
      currentLongitude = locData.longitude;
    });
  }

  int getDistanceInMeters(currLat, currLng, lat, lng) {
    return Geolocator.distanceBetween(
      currLat,
      currLng,
      lat,
      lng,
    ).round();
  }

  bool isValidDistance(RemoteMessage messaging) {
    Map<String, dynamic> data = messaging.data;
    var _list = data.values.toList();
    var lat = double.parse(_list[0]);
    var lng = double.parse(_list[1]);
    print(_list);
    int distance =
        getDistanceInMeters(currentLatitude, currentLongitude, lat, lng);
    var distanceInKm = (distance / 1000).round();

    print('Distance is: ${distanceInKm.toString()}');
    if (distance < 60000) {
      return true;
    }
    return false;
  }

  @override
  void initState() {
    super.initState();
    _getCurrentLocation();
    final messaging = FirebaseMessaging.instance;
    messaging.subscribeToTopic('animal');

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      if (isValidDistance(message)) {
        print('onMessageListen');
      }
    });

    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      if (isValidDistance(message)) {
        print('onMessageOpened');
      }
    });

    FirebaseMessaging.onBackgroundMessage(_messageHandler);
  }
...

【问题讨论】:

  • 目前您收到通知时会记录哪些打印语句?
  • 我不确定我是否理解它。 if (isValidDistance(message)) { print('onMessageOpened'); } 语句在距离有效时打印日志。我写这个语句只是检查我的代码并试图理解发送推送通知。目前我收到所有通知,但我不知道该怎么做
  • 当前打开应用且位置无效时是否收到通知?因为您在使用 onmessage 打开应用程序时处理了通知。但是,对于应用程序关闭时的通知,您需要在后台创建一个特定于平台的通道来处理。查看medium.com/flutter/…
  • 没有。打开应用程序时,我没有收到任何通知。它只是在距离有效时打印日志“onMessageListen”。所以当应用程序关闭时,我无法通过颤动的距离处理推送通知?如果我在 android 和 ios 文件夹中执行此操作,唯一的处理方法是?
  • 是的,这就是它的工作原理。同样通过您的代码,您正在发送一条通知消息,因此当应用程序处于后台时,firebase 会自动显示它。选中此项以获取数据消息和通知消息 firebase.google.com/docs/cloud-messaging/… 之间的区别。您可以使用 FirebaseMessaging.onBackgroundMessage() 在后台处理数据消息,但它必须是顶级函数。

标签: firebase flutter dart google-cloud-functions


【解决方案1】:

首先,您需要将 fcm 从通知消息更改为数据消息,以允许应用在后台处理消息。检查here

exports.myFunction = functions.firestore
.document("animal/{message}")
.onCreate((snapshot, context) => {
  return admin.messaging().sendToTopic("animal", {
    data: {
      latitude: snapshot.data()["latitude"].toString(),
      longitude: snapshot.data()["longitude"].toString(),
      title: snapshot.data().username,
      body: snapshot.data().description,
    },
  });
});

选中here 以在应用打开时显示推送通知。你的代码应该是这样的。

FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (isValidDistance(message)) {
    print('onMessageListen');
    showNotification(message);
  }
});

您可以通过以下方式访问发送的数据

Map<String, dynamic> data = message.data;

那么后台处理程序如下。

_messageHandler(RemoteMessaging message){
if (isValidDistance(message)) {
        print('onMessageListen');
        showNotification(message);
      }
}

或者如下创建一个 Notification 类并使用。

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:geolocator/geolocator.dart';
import 'package:location/location.dart';

const AndroidNotificationChannel channel = AndroidNotificationChannel(
  'high_importance_channel', // id
  'High Importance Notifications', // title
  description: 'This channel is used for important notifications.',
  importance: Importance.max,
);

Future<LocationData> _getCurrentLocation() => Location().getLocation();

int getDistanceInMeters(currLat, currLng, lat, lng) {
  return Geolocator.distanceBetween(
    currLat,
    currLng,
    lat,
    lng,
  ).round();
}

Future<bool> isValidDistance(RemoteMessage messaging) async {
  Map<String, dynamic> data = messaging.data;
  var _list = data.values.toList();
  var lat = double.parse(_list[0]);
  var lng = double.parse(_list[1]);
  print(_list);
  var location = await _getCurrentLocation();
  int distance =
      getDistanceInMeters(location.latitude, location.longitude, lat, lng);
  var distanceInKm = (distance / 1000).round();

  print('Distance is: ${distanceInKm.toString()}');
  if (distance < 60000) {
    return true;
  }
  return false;
}

class NotificationServices {
  final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
    bool isValid = await isValidDistance(message);
    if (isValid) {
      print('onMessageListen');
      showNotification(message);
    }
  }

  backgroundNotification() {
    final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
        FlutterLocalNotificationsPlugin();
    final AndroidInitializationSettings _initialzationSettingsAndriod =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    final IOSInitializationSettings _initialzationSettingsIOS =
        IOSInitializationSettings();
    final InitializationSettings _initializationSettings =
        InitializationSettings(
            android: _initialzationSettingsAndriod,
            iOS: _initialzationSettingsIOS);
    _flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    /// Update the iOS foreground notification presentation options to allow
    /// heads up notifications.
    FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );

    _flutterLocalNotificationsPlugin.initialize(_initializationSettings);

    FirebaseMessaging.instance
        .getInitialMessage()
        .then((RemoteMessage? message) async {
      if (message != null) await onClickNotificationHandler(message);
    });
    FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      bool isValid = await isValidDistance(message);
      if (isValid) {
        print('onMessageListen');
        showNotification(message);
      }
    });
    FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      await onClickNotificationHandler(message);
    });
  }

  onClickNotificationHandler(RemoteMessage message) async {
    Map<String, dynamic> data = message.data;
    print(data);
    //you can handle notificationand navigate to necessary  screen here.
  }

  showNotification(RemoteMessage message) {
    Map<String, dynamic> data = message.data;
    if (data["body"] != null) {
      flutterLocalNotificationsPlugin.show(
        data.hashCode,
        data["title"],
        data["body"],
        NotificationDetails(
          android: AndroidNotificationDetails(
            channel.id,
            channel.name,
            channelDescription: channel.description,
            icon: '@mipmap/ic_launcher',
          ),
          iOS: IOSNotificationDetails(
              presentAlert: true, presentBadge: true, presentSound: true),
        ),
      );
    }
  }
}

【讨论】:

  • 感谢您的回答。它帮助我在打开应用程序时获得距离通知。但不幸的是,我收到错误 onBackgroundMessage: Failed to handle method call E/MethodChannel#lyokone/location( 8008): java.lang.NullPointerException: Attempt to write to field 'io.flutter.plugin.common.MethodChannel$Result com.lyokone.location.FlutterLocation.getLocationResult' on a null object reference 当我尝试调用方法 isValidDistance(message) 时收到此错误。
  • 对了应该怎么调用Notification类呢?我试图在 main.dart 中调用 initState,但它没有发送任何通知。 @override void initState() { super.initState(); NotificationServices();
  • 是 initstate NotificationService().backgroundNotification();
  • 对于例外情况,我认为您可能需要实现 platfrm 特定代码。
猜你喜欢
  • 2013-10-24
  • 1970-01-01
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
  • 2022-07-04
  • 1970-01-01
  • 2018-02-17
  • 2018-01-02
相关资源
最近更新 更多