Flutter- FCM con notifica e avviso locali

Dec 06 2020

È la prima volta che provo FCM con Flutter. Ho controllato alcune delle domande e dei documenti SO da GitHub.

Sono in grado di inviare le notifiche e vengono recapitate quando l'app non è in esecuzione.

Se l'app è in esecuzione o in background, i messaggi non sono visibili.

Ho aggiunto il codice nel file main.dart ma non sono sicuro che questo sia il modo corretto o meno.

Modifica: questo è per 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}}

Nel codice sottostante, sto cercando di utilizzare le notifiche locali con 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');
  }
}

Errore

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

Modifica 2: ho scavato di più e ho trovato il codice seguente.

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()),
    );
  }

Con il codice sopra posso vedere le notifiche nella barra delle notifiche ma nella sezione Riprendi voglio il reindirizzamento che non funziona. Non so perché.

Inoltre voglio mostrare la casella di avviso negli eventi onmeesage e onresume.

Risposte

1 Chris Dec 11 2020 at 20:39

Guardando il tuo codice (ultimo modificato) sopra, penso che prima devi assicurarti se vengono utilizzate le notifiche locali o quella predefinita fcm. Dato che il tuo myBackgroundMessageHandler non fa nulla, presumo quest'ultimo. Prova a sostituire temporaneamente il titolo con una stringa fissa (ad esempio "questo è locale") per assicurarti.

In secondo luogo, myBackgroundMessageHandler verrà chiamato solo per i messaggi di dati. Se usi il payload che hai scritto all'inizio, dovresti stare bene. In ogni caso assicurati di non inserire titolo, corpo, informazioni sullo stile ecc. Direttamente nel payload. Se ne hai bisogno, inseriscilo nel nodo dati.

Questo è il codice che sto usando:

calling the notificationService init() method in main.dart

notification-service.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,
    );
  }
}

data-notification.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
}

Puoi ignorare la parte del modello DataNotification e analizzare la notifica da solo, l'ho appena usata per alcune interazioni aggiuntive con nel back-end.

Questo funziona bene per me, tuttavia, se vuoi mostrare un avviso per "onSelectNotification" o simili, devi trovare un modo per ottenere il contesto lì. Non (ancora) sicuro di come farlo.

EDIT: Puoi chiamarlo in questo modo in main.dart

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

  NotificationService().init();

  runApp(
    MyApp()
  );
}

Tieni presente che attualmente esiste un problema con la messaggistica in background e il ricaricamento a caldo: https://github.com/FirebaseExtended/flutterfire/issues/4316

1 Farhana Dec 09 2020 at 22:41

Il carico utile deve essere corretto notificatione l' dataoggetto all'interno del carico utile deve contenere la chiave titlee body. otterrai titlee bodynull quando la tua app è chiusa in notificationchiave in quella situazione dovresti avere titolo e corpo nella chiave dati laterale.

{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: }}

e non mettere il titolo e il corpo nulli

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);
  }

per il reindirizzamento

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) {});
    }

  }

in 'onSelectNotification' puoi passare la tua condizione nella stringa parama e puoi reindirizzare

(facoltativo, ma consigliato) Se desideri ricevere una notifica nella tua app (tramite onResume e onLaunch, vedi sotto) quando l'utente fa clic su una notifica nella barra delle applicazioni, includi il seguente filtro di intenti all'interno del tag del tuo android / app / src /main/AndroidManifest.xml: