Flutter-具有本地通知和警报的 FCM [英] Flutter- FCM with Local notification and alert

查看:23
本文介绍了Flutter-具有本地通知和警报的 FCM的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

This is first time I am testing FCM with Flutter. I checked some of the SO questions and documents from GitHub.

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

I am able to send the notifications and they are getting delivered when the app is not running.

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

If app is running or in the background then messages are not visible.

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

I have added the code in main.dart file but not sure this is the correct way or not.

这是针对 onResume 的:

This is for 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 一起使用.

In the Below code, i am trying to use local notifications with 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:我做了更多的挖掘并想出了下面的代码.

Edit 2: I did more digging and come up with the below code.

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 部分我想重定向不起作用.不知道为什么.

With above code i can see notifications in notification bar but onresume section i want to redirection that is not working. Not sure why.

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

Also I want to show alert box in onmeesage and onresume events.

推荐答案

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

Looking at your (last edited) code above, I think first you have to make sure, whether localnotifications is used or the default fcm one. Since your myBackgroundMessageHandler does not do anything, I assume the latter one. Try replacing the title temporarily with a fixed string (e.g. "this is a local one") to make sure.

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

Secondly, myBackgroundMessageHandler will only be called for data messages. If you use the payload you wrote in the beginning, you should be fine. Anyway make sure to not put title, body, style-information etc directly in the payload. If you need it, put it in the data node.

这是我正在使用的代码:

This is the code I am using:

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 模型部分,自己解析通知,我只是在后端使用它进行一些额外的交互.

You can ignore the DataNotification model part, and parse the notification yourself, I just used it for some additional interactions with in the backend.

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

This works well for me, however, if you want to show an alert for "onSelectNotification" or alike, you need to find a way to get the context there. Not (yet) sure, how to do that.

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

You can call it like this in main.dart

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

  NotificationService().init();

  runApp(
    MyApp()
  );
}

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

Be aware that there is currently an issue with backgroundmessaging and hot-reloading: https://github.com/FirebaseExtended/flutterfire/issues/4316

这篇关于Flutter-具有本地通知和警报的 FCM的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆