Flutter - FCM с локальным уведомлением и предупреждением
Я впервые тестирую FCM с Flutter. Я проверил некоторые вопросы SO и документы с GitHub.
Я могу отправлять уведомления, и они доставляются, когда приложение не работает.
Если приложение запущено или работает в фоновом режиме, сообщения не отображаются.
Я добавил код в файл 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()),
);
}
С приведенным выше кодом я могу видеть уведомления в панели уведомлений, но в разделе возобновления я хочу перенаправление, которое не работает. Не знаю почему.
Также я хочу отображать окно предупреждения в событиях onmeesage и onresume.
Ответы
Глядя на ваш (последний отредактированный) код выше, я думаю, сначала вы должны убедиться, используются ли локальные уведомления или fcm по умолчанию. Поскольку ваш myBackgroundMessageHandler ничего не делает, я предполагаю, что последний. Попробуйте временно заменить заголовок фиксированной строкой (например, «это локальный»), чтобы убедиться.
Во-вторых, myBackgroundMessageHandler будет вызываться только для сообщений с данными. Если вы используете полезную нагрузку, которую написали в начале, все будет в порядке. В любом случае не помещайте заголовок, текст, информацию о стиле и т. Д. Непосредственно в полезную нагрузку. Если он вам нужен, поместите его в узел данных.
Вот код, который я использую:
calling the notificationService init() method in main.dart
уведомление-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
}
Вы можете игнорировать часть модели DataNotification и самостоятельно анализировать уведомление, я просто использовал его для некоторых дополнительных взаимодействий с серверной частью.
Это хорошо работает для меня, однако, если вы хотите показать предупреждение для «onSelectNotification» или аналогичного, вам нужно найти способ получить там контекст. Не уверен (пока), как это сделать.
РЕДАКТИРОВАТЬ: вы можете назвать это так в main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
NotificationService().init();
runApp(
MyApp()
);
}
Имейте в виду, что в настоящее время существует проблема с фоновыми сообщениями и горячей перезагрузкой: https://github.com/FirebaseExtended/flutterfire/issues/4316
Ваша нагрузка должна быть правильной, notification
и data
объект внутри полезной нагрузки должен содержать title
и body
ключ. вы получите title
и body
null, когда ваше приложение будет закрыто в notification
ключе, в этой ситуации у вас должны быть заголовок и тело в ключе побочных данных.
{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 / app / src /main/AndroidManifest.xml: