【问题标题】:Flutter- FCM with Local notification and alertFlutter-具有本地通知和警报的 FCM
【发布时间】:2021-03-17 20:41:30
【问题描述】:

这是我第一次使用 Flutter 测试 FCM。我检查了一些来自 GitHub 的 SO 问题和文档。

我能够发送通知,并且当应用程序未运行时它们会被传递。

如果应用正在运行或在后台运行,则消息不可见。

我在 main.dart 文件中添加了代码,但不确定这是否正确。

编辑: 这是针对 onResume 的:

{notification: {}, data: {badge: 1, collapse_key: com.HT, google.original_priority: high, google.sent_time: 1623238, google.delivered_priority: high, sound: default, google.ttl: 2419200, from: 71374876, body: Body, title: Title, click_action: FLUTTER_NOTIFICATION_CLICK, google.message_id: 0:50a56}}

在下面的代码中,我尝试将本地通知与 FCM 一起使用。

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

class _MyAppState extends State<MyApp> {
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      new FlutterLocalNotificationsPlugin();

  @override
  void initState() {
    var initializationSettingsAndroid =
        new AndroidInitializationSettings('@mipmap/ic_launcher');
    var initializationSettingsIOS = new IOSInitializationSettings();
    var initializationSettings = new InitializationSettings(
        android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: onSelectNotification);
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        showNotification(
            message['notification']['title'], message['notification']['body']);
        print("onMessage: $message");
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
        // Navigator.pushNamed(context, '/notify');
        ExtendedNavigator.of(context).push(
          Routes.bookingQRScan,
        );
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
      },
    );
  }

  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: AnimatedSplashScreen(), //SplashScreen()

      builder: ExtendedNavigator.builder<a.Router>(router: a.Router()),
    );
  }

  Future onSelectNotification(String payload) async {
    showDialog(
      context: context,
      builder: (_) {
        return new AlertDialog(
          title: Text("PayLoad"),
          content: Text("Payload : $payload"),
        );
      },
    );
  }

  void showNotification(String title, String body) async {
    await _demoNotification(title, body);
  }

  Future<void> _demoNotification(String title, String body) async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'channel_ID', 'channel name', 'channel description',
        importance: Importance.max,
        playSound: false, //true,
        //sound: 'sound',
        showProgress: true,
        priority: Priority.high,
        ticker: 'test ticker');

    //var iOSChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin
        .show(0, title, body, platformChannelSpecifics, payload: 'test');
  }
}

错误

This is when my app is running on foreground. E/FlutterFcmService(14434): Fatal: failed to find callback
W/FirebaseMessaging(14434): Missing Default Notification Channel metadata in AndroidManifest. Default value will be used.
W/ConnectionTracker(14434): Exception thrown while unbinding
W/ConnectionTracker(14434): java.lang.IllegalArgumentException: Service not registered: lu@fb04880
Notification is visible in notification center. Now i am clicking on it and app get terminated.
and new instance of app is running and below is the return code. I/flutter (14434): onResume: {notification: {}, data: {badge: 1, collapse_key: com.HT, google.original_priority: high, google.sent_time: 1607733798, google.delivered_priority: high, sound: default, google.ttl: 2419200, from: 774876, body: Body, title: Title, click_action: FLUTTER_NOTIFICATION_CLICK, google.message_id: 0:1607573733816296%850a56}}
E/FlutterFcmService(14434): Fatal: failed to find callback
W/ConnectionTracker(14434): Exception thrown while unbinding

编辑 2: 我做了更多的挖掘并想出了下面的代码。

final FirebaseMessaging _fcm = FirebaseMessaging();

  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  var initializationSettingsAndroid;
  var initializationSettingsIOS;
  var initializationSettings;

  void _showNotification() async {
    //await _buildNotification();
  }

  Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
    if (message.containsKey('data')) {
      // Handle data message
      final dynamic data = message['data'];
    }

    if (message.containsKey('notification')) {
      // Handle notification message

      final dynamic notification = message['notification'];
    }

    // Or do other work.
  }

  Future<void> _createNotificationChannel(
      String id, String name, String description) async {
    final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    var androidNotificationChannel = AndroidNotificationChannel(
      id,
      name,
      description,
      importance: Importance.max,
      playSound: true,
      // sound: RawResourceAndroidNotificationSound('not_kiddin'),
      enableVibration: true,
    );
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(androidNotificationChannel);
  }

  Future<void> _buildNotification(String title, String body) async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'my_channel', 'Channel Name', 'Channel Description.',
        importance: Importance.max,
        priority: Priority.high,
        //  playSound: true,
        enableVibration: true,
        //  sound: RawResourceAndroidNotificationSound('not_kiddin'),
        ticker: 'noorderlicht');
    //var iOSChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);

    await flutterLocalNotificationsPlugin
        .show(0, title, body, platformChannelSpecifics, payload: 'payload');
  }

  @override
  void initState() {
    super.initState();

    initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    initializationSettingsIOS = IOSInitializationSettings(
        onDidReceiveLocalNotification: onDidReceiveLocalNotification);

    initializationSettings =
        InitializationSettings(android: initializationSettingsAndroid);
    // initializationSettingsAndroid, initializationSettingsIOS);

    _fcm.requestNotificationPermissions();

    _fcm.configure(
      onMessage: (Map<String, dynamic> message) async {
        print(message);
        flutterLocalNotificationsPlugin.initialize(initializationSettings,
            onSelectNotification: onSelectNotification);

        //_showNotification();
        Map.from(message).map((key, value) {
          print(key);
          print(value);
          print(value['title']);
          _buildNotification(value['title'], value['body']);
        });
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
        print(message['data']['title']);
        //AlertDialog(title: message['data']['title']);
        ExtendedNavigator.of(context).push(
          Routes.bookingQRScan,
        );
        //_showNotification();
      },
    );
  }

  Future onDidReceiveLocalNotification(
      int id, String title, String body, String payload) async {
    // display a dialog with the notification details, tap ok to go to another page
    showDialog(
      context: context,
      builder: (BuildContext context) => CupertinoAlertDialog(
        title: Text(title),
        content: Text(body),
        actions: [
          CupertinoDialogAction(
            isDefaultAction: true,
            child: Text('Ok'),
            onPressed: () {},
          )
        ],
      ),
    );
  }

  Future onSelectNotification(String payload) async {
    if (payload != null) {
      debugPrint('Notification payload: $payload');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: AnimatedSplashScreen(), //SplashScreen()

      builder: ExtendedNavigator.builder<a.Router>(router: a.Router()),
    );
  }

使用上面的代码,我可以在通知栏中看到通知,但在 onresume 部分我想重定向不起作用。不知道为什么。

我还想在 onmeesage 和 onresume 事件中显示警报框。

【问题讨论】:

  • 请发布您的有效载荷
  • 已在问题中添加。
  • 你的payload是错误的,因为我的经验是通知键必须有title和body参数和数据json对象也应该包含title和body key。
  • 好的,感谢您的评论。我根据您的建议进行了更多更改并进行了测试。还有一件事,如果应用程序在后台,那么我是否会在单击通知后在应用程序中收到警报,否则它只会打开应用程序。
  • 您可以重定向到您想要的任何屏幕,如果您不设置任何条件,那么它将仅打开应用程序。

标签: flutter firebase-cloud-messaging


【解决方案1】:

您的有效载荷必须正确,有效载荷内的notificationdata 对象必须包含titlebody 键。当您的应用在notification 键中关闭时,您将得到titlebody null,在这种情况下,您应该在侧数据键中包含标题和正文。

{notification: {title: title, body: test}, data: {notification_type: Welcome, body: body, badge: 1, sound: , title: farhana mam, click_action: FLUTTER_NOTIFICATION_CLICK, message: H R U, category_id: 2, product_id: 1, img_url: }}

并且不要将标题和正文设为空

void showNotification(Map<String, dynamic> msg) async {
    //{notification: {title: title, body: test}, data: {notification_type: Welcome, body: body, badge: 1, sound: , title: farhana mam, click_action: FLUTTER_NOTIFICATION_CLICK, message: H R U, category_id: 2, product_id: 1, img_url: }}
    print(msg);
    print(msg['data']['title']);
    var title = msg['data']['title'];
    var msge = msg['data']['body'];

    var android = new AndroidNotificationDetails(
        'channel id', 'channel NAME', 'CHANNEL DESCRIPTION',
        priority: Priority.High, importance: Importance.Max);
    var iOS = new IOSNotificationDetails();
    var platform = new NotificationDetails(android, iOS);
    await flutterLocalNotificationsPlugin.show(0, title, msge, platform,
        payload: msge);
  }

用于重定向

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
    var android = new AndroidInitializationSettings('@mipmap/ic_launcher');
    var iOS = new IOSInitializationSettings();
    var initSetttings = new InitializationSettings(android, iOS);
    flutterLocalNotificationsPlugin.initialize(initSetttings, onSelectNotification: onSelectNotification);
    firebaseCloudMessaging_Listeners();

  Future onSelectNotification(String payload) async {
    if (payload != null) {
      debugPrint('notification payload:------ ${payload}');
      await Navigator.push(
        context,
        new MaterialPageRoute(builder: (context) => NotificationListing()),
      ).then((value) {});
    }

  }

在“onSelectNotification”中,您可以在字符串参数中传递您的条件,您可以重定向

(可选,但推荐)如果想要在用户单击系统托盘中的通知时在您的应用中收到通知(通过 onResume 和 onLaunch,见下文),请在您的 android/ 标签中包含以下意图过滤器应用程序/src/main/AndroidManifest.xml:

【讨论】:

  • 我是否只需要定义一次 _fcm.configure 或需要在 onmessage 期间显示通知的任何地方?还有一些关于 onResume 期间重定向的建议。
  • 我更新了你可以使用'onSelectNotification'方法重定向的代码,你可以在主屏幕上配置fcm。
  • 让我看看你的代码。如果我卡在任何地方,我会告诉你的。感谢您的帮助 Farhana。
  • 我试过你的代码,但还是不行。我可能需要您提供更多代码。
  • 您遇到什么问题,您是否测试过您的 Firebase 以从 Firebase 控制台发送虚拟通知。
【解决方案2】:

查看您上面的(最后编辑的)代码,我认为首先您必须确定是使用 localnotifications 还是使用默认的 fcm 。由于您的 myBackgroundMessageHandler 没有做任何事情,我假设是后者。尝试用固定字符串临时替换标题(例如“这是本地的”)以确保。

其次,myBackgroundMessageHandler 只会为数据消息调用。如果你使用一开始写的payload,应该没问题。无论如何,请确保不要将标题、正文、样式信息等直接放在有效负载中。需要的话,放到data节点里面。

这是我正在使用的代码:

calling the notificationService init() method in main.dart

通知服务.dart

import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:app/models/data-notification.dart';
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

FlutterLocalNotificationsPlugin notificationsPlugin =
    FlutterLocalNotificationsPlugin();

//Function to handle Notification data in background. 
Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
  print("FCM backgroundMessageHandler $message");
  showNotification(DataNotification.fromPushMessage(message['data']));
  return Future<void>.value();
}

//Function to handle Notification Click.
Future<void> onSelectNotification(String payload) {
  print("FCM onSelectNotification");
  return Future<void>.value();
}

//Function to Parse and Show Notification when app is in foreground
Future<dynamic> onMessage(Map<String, dynamic> message) {
  print("FCM onMessage $message");
  showNotification(DataNotification.fromPushMessage(message['data']));
  return Future<void>.value();
}

//Function to Handle notification click if app is in background
Future<dynamic> onResume(Map<String, dynamic> message) {
  print("FCM onResume $message");
  return Future<void>.value();
}

//Function to Handle notification click if app is not in foreground neither in background
Future<dynamic> onLaunch(Map<String, dynamic> message) {
  print("FCM onLaunch $message");
  return Future<void>.value();
}

void showNotification(DataNotification notification) async {
  final AndroidNotificationDetails androidPlatformChannelSpecifics =
      await getAndroidNotificationDetails(notification);

  final NotificationDetails platformChannelSpecifics =
      NotificationDetails(android: androidPlatformChannelSpecifics);

  await notificationsPlugin.show(
    0,
    notification.title,
    notification.body,
    platformChannelSpecifics,
  );
}

Future<AndroidNotificationDetails> getAndroidNotificationDetails(
    DataNotification notification) async {
  switch (notification.notificationType) {
    case NotificationType.NEW_INVITATION:
    case NotificationType.NEW_MEMBERSHIP:
    case NotificationType.NEW_ADMIN_ROLE:
    case NotificationType.MEMBERSHIP_BLOCKED:
    case NotificationType.MEMBERSHIP_REMOVED:
    case NotificationType.NEW_MEMBERSHIP_REQUEST:
      return AndroidNotificationDetails(
          'organization',
          'Organization management',
          'Notifications regarding your organizations and memberships.',
          importance: Importance.max,
          priority: Priority.high,
          showWhen: false,
          category: "Organization",
          icon: 'my_app_icon_simple',
          largeIcon: DrawableResourceAndroidBitmap('my_app_icon'),
          styleInformation: await getBigPictureStyle(notification),
          sound: RawResourceAndroidNotificationSound('slow_spring_board'));
    case NotificationType.NONE:
    default:
      return AndroidNotificationDetails('general', 'General notifications',
          'General notifications that are not sorted to any specific topics.',
          importance: Importance.max,
          priority: Priority.high,
          showWhen: false,
          category: "General",
          icon: 'my_app_icon_simple',
          largeIcon: DrawableResourceAndroidBitmap('my_app_icon'),
          styleInformation: await getBigPictureStyle(notification),
          sound: RawResourceAndroidNotificationSound('slow_spring_board'));
  }
}

Future<BigPictureStyleInformation> getBigPictureStyle(
    DataNotification notification) async {
  if (notification.imageUrl != null) {
    print("downloading");
    final String bigPicturePath =
        await _downloadAndSaveFile(notification.imageUrl, 'bigPicture');

    return BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath),
        hideExpandedLargeIcon: true,
        contentTitle: notification.title,
        htmlFormatContentTitle: false,
        summaryText: notification.body,
        htmlFormatSummaryText: false);
  } else {
    print("NOT downloading");
    return null;
  }
}

Future<String> _downloadAndSaveFile(String url, String fileName) async {
  final Directory directory = await getApplicationDocumentsDirectory();
  final String filePath = '${directory.path}/$fileName';
  final http.Response response = await http.get(url);
  final File file = File(filePath);
  await file.writeAsBytes(response.bodyBytes);
  return filePath;
}

class NotificationService {

  FirebaseMessaging _fcm = FirebaseMessaging();

  void init() async {
    final AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');

    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings();

    final InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    await notificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (value) => onSelectNotification(value));

    _fcm.configure(
      onMessage: onMessage,
      onBackgroundMessage: backgroundMessageHandler,
      onLaunch: onLaunch,
      onResume: onResume,
    );
  }
}

数据通知.dart

import 'package:enum_to_string/enum_to_string.dart';

class DataNotification {
  final String id;
  final String title;
  final String body;
  final NotificationType notificationType;
  final String imageUrl;
  final dynamic data;
  final DateTime readAt;
  final DateTime createdAt;
  final DateTime updatedAt;

  DataNotification({
    this.id,
    this.title,
    this.body,
    this.notificationType,
    this.imageUrl,
    this.data,
    this.readAt,
    this.createdAt,
    this.updatedAt,
  });

  factory DataNotification.fromPushMessage(dynamic data) {
    return DataNotification(
      id: data['id'],
      title: data['title'],
      body: data['body'],
      notificationType: EnumToString.fromString(
          NotificationType.values, data['notification_type']),
      imageUrl: data['image_url'] ?? null,
      data: data,
      readAt: null,
      createdAt: null,
      updatedAt: null,
    );
  }
}

enum NotificationType {
  NONE,
  NEW_INVITATION,
  NEW_MEMBERSHIP,
  NEW_ADMIN_ROLE,
  MEMBERSHIP_BLOCKED,
  MEMBERSHIP_REMOVED,
  NEW_MEMBERSHIP_REQUEST
}

你可以忽略 DataNotification 模型部分,自己解析通知,我只是在后端使用它进行一些额外的交互。

这对我来说很有效,但是,如果您想显示“onSelectNotification”或类似的警报,您需要找到一种方法来获取那里的上下文。 (还)不确定,该怎么做。

编辑: 你可以在main.dart中这样调用它

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  NotificationService().init();

  runApp(
    MyApp()
  );
}

请注意,背景消息和热重载目前存在问题:https://github.com/FirebaseExtended/flutterfire/issues/4316

【讨论】:

  • 谢谢克里斯。我会阅读你的代码并让你知道。我还想知道一件事。如果我在不同的页面上,那么我是否会收到通知,或者我需要在该页面上定义它,或者需要创建某种服务。
  • 不确定我是否理解正确。没有必要在某个页面上初始化 fcm。您可以在 main.dart 中执行此操作,也可以通过上述服务执行此操作。在页面上执行此操作,可能会在状态等方面出现不必要的问题,不确定。至少您在 backgroundMessageHandler 中使用的所有函数显然必须是全局的或静态的——尽管我找不到在哪里读到那个 atm。
  • 嗨,克里斯,对不起,我离开了几天。现在我回来并使用您的代码,因为它只是用于测试目的,稍后将根据需要对其进行更改。关于在 main.dart 中调用 notificationService init() 方法,我只想知道一件事。您正在谈论此 NotificationService() 或其他内容。
  • 我将 NotificationService 设置为这样,但在调试过程中我没有看到它被触发。无效的 initState() { super.initState();通知服务(); }
  • 感谢克里斯的更新。我已经在使用 io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"));在应用程序 kotlen 文件中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-30
  • 1970-01-01
  • 1970-01-01
  • 2021-01-21
  • 2023-02-25
  • 2021-08-25
  • 1970-01-01
相关资源
最近更新 更多