Flutter - FCM avec notification et alerte locales
C'est la première fois que je teste FCM avec Flutter. J'ai vérifié certaines des questions et documents SO de GitHub.
Je peux envoyer les notifications et elles sont livrées lorsque l'application n'est pas en cours d'exécution.
Si l'application est en cours d'exécution ou en arrière-plan, les messages ne sont pas visibles.
J'ai ajouté le code dans le fichier main.dart mais je ne suis pas sûr que ce soit la bonne manière ou non.
Edit: Ceci est pour 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}}
Dans le code ci-dessous, j'essaie d'utiliser les notifications locales avec 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');
}
}
Erreur
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
Edit 2: J'ai creusé davantage et j'ai trouvé le code ci-dessous.
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()),
);
}
Avec le code ci-dessus, je peux voir les notifications dans la barre de notification, mais dans la section de reprise, je veux une redirection qui ne fonctionne pas. Pas certain de pourquoi.
Je veux également afficher la boîte d'alerte dans les événements onmeesage et onresume.
Réponses
En regardant votre code (dernière modification) ci-dessus, je pense que vous devez d'abord vous assurer que les notifications locales sont utilisées ou celles par défaut de fcm. Puisque votre myBackgroundMessageHandler ne fait rien, je suppose que ce dernier. Essayez de remplacer temporairement le titre par une chaîne fixe (par exemple "c'est une chaîne locale") pour vous en assurer.
Deuxièmement, myBackgroundMessageHandler ne sera appelé que pour les messages de données. Si vous utilisez la charge utile que vous avez écrite au début, tout devrait bien se passer. Quoi qu'il en soit, assurez-vous de ne pas mettre le titre, le corps, les informations de style, etc. directement dans la charge utile. Si vous en avez besoin, placez-le dans le nœud de données.
Voici le code que j'utilise:
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
}
Vous pouvez ignorer la partie du modèle DataNotification et analyser la notification vous-même.Je l'ai juste utilisée pour des interactions supplémentaires avec le backend.
Cela fonctionne bien pour moi, cependant, si vous voulez afficher une alerte pour "onSelectNotification" ou similaire, vous devez trouver un moyen d'y amener le contexte. Pas (encore) sûr, comment faire ça.
EDIT: Vous pouvez l'appeler comme ça dans main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
NotificationService().init();
runApp(
MyApp()
);
}
Sachez qu'il existe actuellement un problème avec la messagerie en arrière-plan et le rechargement à chaud: https://github.com/FirebaseExtended/flutterfire/issues/4316
Votre charge utile doit être correcte notification
et l' data
objet à l'intérieur de la charge utile doit contenir la clé title
et body
. vous obtiendrez title
et body
null lorsque votre application est fermée en notification
clé dans cette situation, vous devriez avoir le titre et le corps dans la clé de données latérale.
{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: }}
et ne mettez pas le titre et le corps nuls
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);
}
pour la redirection
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) {});
}
}
dans 'onSelectNotification', vous pouvez passer votre condition dans la chaîne parama et vous pouvez rediriger
(facultatif, mais recommandé) Si vous souhaitez être averti dans votre application (via onResume et onLaunch, voir ci-dessous) lorsque l'utilisateur clique sur une notification dans la barre d'état système, incluez le filtre d'intention suivant dans la balise de votre android / app / src /main/AndroidManifest.xml: