FCM 仅在应用程序被终止时在托盘中发送消息,但我需要声音通知
FCM only sends message in tray when app is killed but i need notifications with sound
我正在使用从 firebase_messaging 包的示例部分找到的一个简单的 firebase 消息传递示例。
main.dart
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
final Map<String, Item> _items = <String, Item>{};
Item _itemForMessage(Map<String, dynamic> message) {
final dynamic data = message['data'] ?? message;
final String itemId = data['id'];
final Item item = _items.putIfAbsent(itemId, () => Item(itemId: itemId))
..status = data['status'];
return item;
}
class Item {
Item({this.itemId});
final String itemId;
StreamController<Item> _controller = StreamController<Item>.broadcast();
Stream<Item> get onChanged => _controller.stream;
String _status;
String get status => _status;
set status(String value) {
_status = value;
_controller.add(this);
}
static final Map<String, Route<void>> routes = <String, Route<void>>{};
Route<void> get route {
final String routeName = '/detail/$itemId';
return routes.putIfAbsent(
routeName,
() => MaterialPageRoute<void>(
settings: RouteSettings(name: routeName),
builder: (BuildContext context) => DetailPage(itemId),
),
);
}
}
class DetailPage extends StatefulWidget {
DetailPage(this.itemId);
final String itemId;
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
Item _item;
StreamSubscription<Item> _subscription;
@override
void initState() {
super.initState();
_item = _items[widget.itemId];
_subscription = _item.onChanged.listen((Item item) {
if (!mounted) {
_subscription.cancel();
} else {
setState(() {
_item = item;
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Item ${_item.itemId}"),
),
body: Material(
child: Center(child: Text("Item status: ${_item.status}")),
),
);
}
}
class PushMessagingExample extends StatefulWidget {
@override
_PushMessagingExampleState createState() => _PushMessagingExampleState();
}
class _PushMessagingExampleState extends State<PushMessagingExample> {
String _homeScreenText = "Waiting for token...";
bool _topicButtonsDisabled = false;
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
final TextEditingController _topicController =
TextEditingController(text: 'topic');
Widget _buildDialog(BuildContext context, Item item) {
return AlertDialog(
content: Text("Item ${item.itemId} has been updated"),
actions: <Widget>[
FlatButton(
child: const Text('CLOSE'),
onPressed: () {
Navigator.pop(context, false);
},
),
FlatButton(
child: const Text('SHOW'),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
}
void _showItemDialog(Map<String, dynamic> message) {
showDialog<bool>(
context: context,
builder: (_) => _buildDialog(context, _itemForMessage(message)),
).then((bool shouldNavigate) {
if (shouldNavigate == true) {
_navigateToItemDetail(message);
}
});
}
void _navigateToItemDetail(Map<String, dynamic> message) {
final Item item = _itemForMessage(message);
// Clear away dialogs
Navigator.popUntil(context, (Route<dynamic> route) => route is PageRoute);
if (!item.route.isCurrent) {
Navigator.push(context, item.route);
}
}
@override
void initState() {
super.initState();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
_showItemDialog(message);
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
_navigateToItemDetail(message);
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
_navigateToItemDetail(message);
},
);
_firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(
sound: true, badge: true, alert: true, provisional: true));
_firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings settings) {
print("Settings registered: $settings");
});
_firebaseMessaging.getToken().then((String token) {
assert(token != null);
setState(() {
_homeScreenText = "Push Messaging token: $token";
});
print(_homeScreenText);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Push Messaging Demo'),
),
// For testing -- simulate a message being received
floatingActionButton: FloatingActionButton(
onPressed: () => _showItemDialog(<String, dynamic>{
"data": <String, String>{
"id": "2",
"status": "out of stock",
},
}),
tooltip: 'Simulate Message',
child: const Icon(Icons.message),
),
body: Material(
child: Column(
children: <Widget>[
Center(
child: Text(_homeScreenText),
),
Row(children: <Widget>[
Expanded(
child: TextField(
controller: _topicController,
onChanged: (String v) {
setState(() {
_topicButtonsDisabled = v.isEmpty;
});
}),
),
FlatButton(
child: const Text("subscribe"),
onPressed: _topicButtonsDisabled
? null
: () {
_firebaseMessaging
.subscribeToTopic(_topicController.text);
_clearTopicText();
},
),
FlatButton(
child: const Text("unsubscribe"),
onPressed: _topicButtonsDisabled
? null
: () {
_firebaseMessaging
.unsubscribeFromTopic(_topicController.text);
_clearTopicText();
},
),
----------
])
],
),
));
}
void _clearTopicText() {
setState(() {
_topicController.text = "";
_topicButtonsDisabled = true;
});
}
}
void main() {
runApp(
MaterialApp(
home: PushMessagingExample(),
),
);
}
一切正常,但通知没有按我的意愿出现。
我需要如下通知:
它会出现大约 2-3 秒,然后如果我们忽略它,我们可以在托盘中查看它。
[1]: https://i.stack.imgur.com/vfeSd.png
但我只是在顶部栏的时钟旁边看到颤动图标,而不是这个,我必须拖动并检查托盘以检查通知。
所以,请告诉我如何在顶部显示带有声音和小横幅的通知,该通知应显示几秒钟,然后如果通知被忽略,应保留在托盘中。
你已经按照官方文档中提到的那样做了,但请相信我,官方文档是如此死板和不完整。
您需要执行几个步骤才能显示通知横幅:
来源和学分: https://github.com/FirebaseExtended/flutterfire/issues/1327#issuecomment-623399564
将以下内容添加到您的 strings.xml
。当然,您会希望为自己的应用自定义这些内容,并注意频道名称和频道描述是用户将在其通知设置中看到的内容。
<string name="notification_channel_id" translatable="false">my_unique_fcm_id</string>
<string name="notification_channel_name">The name of this notification</string>
<string name="notification_channel_desc">A description of the notification.</string>
通过将以下代码添加到您的应用程序来注册新的通知渠道。这应该尽早完成,例如,在 MainActivity.kt
(或 .java)中调用 super.onCreate 之后。
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelID = getString(R.string.notification_channel_id)
val name = getString(R.string.notification_channel_name)
val descriptionText = getString(R.string.notification_channel_desc)
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(channelID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
注意设置重要性的行,因为这是导致此频道上发送的通知显示为“注意”通知的原因。
val importance = NotificationManager.IMPORTANCE_HIGH
将以下内容添加到您的 AndroidManifest.xml 标签下。这将确保任何在其有效负载中未设置通知通道的 FCM 都将使用此通道。 (optional)
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/notification_channel_id" />
清理并重建
发送测试 FCM。它应该默认使用您新创建的频道并显示为“注意”通知。
(可选)您可以重复这些步骤,为您计划发送的每种通知类型设置更多通知渠道。只需确保每个都有一个唯一的 ID,并且在发送时将通道 ID 与通知负载捆绑在一起。
完整的文档在这里。
我正在使用从 firebase_messaging 包的示例部分找到的一个简单的 firebase 消息传递示例。
main.dart
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
final Map<String, Item> _items = <String, Item>{};
Item _itemForMessage(Map<String, dynamic> message) {
final dynamic data = message['data'] ?? message;
final String itemId = data['id'];
final Item item = _items.putIfAbsent(itemId, () => Item(itemId: itemId))
..status = data['status'];
return item;
}
class Item {
Item({this.itemId});
final String itemId;
StreamController<Item> _controller = StreamController<Item>.broadcast();
Stream<Item> get onChanged => _controller.stream;
String _status;
String get status => _status;
set status(String value) {
_status = value;
_controller.add(this);
}
static final Map<String, Route<void>> routes = <String, Route<void>>{};
Route<void> get route {
final String routeName = '/detail/$itemId';
return routes.putIfAbsent(
routeName,
() => MaterialPageRoute<void>(
settings: RouteSettings(name: routeName),
builder: (BuildContext context) => DetailPage(itemId),
),
);
}
}
class DetailPage extends StatefulWidget {
DetailPage(this.itemId);
final String itemId;
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
Item _item;
StreamSubscription<Item> _subscription;
@override
void initState() {
super.initState();
_item = _items[widget.itemId];
_subscription = _item.onChanged.listen((Item item) {
if (!mounted) {
_subscription.cancel();
} else {
setState(() {
_item = item;
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Item ${_item.itemId}"),
),
body: Material(
child: Center(child: Text("Item status: ${_item.status}")),
),
);
}
}
class PushMessagingExample extends StatefulWidget {
@override
_PushMessagingExampleState createState() => _PushMessagingExampleState();
}
class _PushMessagingExampleState extends State<PushMessagingExample> {
String _homeScreenText = "Waiting for token...";
bool _topicButtonsDisabled = false;
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
final TextEditingController _topicController =
TextEditingController(text: 'topic');
Widget _buildDialog(BuildContext context, Item item) {
return AlertDialog(
content: Text("Item ${item.itemId} has been updated"),
actions: <Widget>[
FlatButton(
child: const Text('CLOSE'),
onPressed: () {
Navigator.pop(context, false);
},
),
FlatButton(
child: const Text('SHOW'),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
}
void _showItemDialog(Map<String, dynamic> message) {
showDialog<bool>(
context: context,
builder: (_) => _buildDialog(context, _itemForMessage(message)),
).then((bool shouldNavigate) {
if (shouldNavigate == true) {
_navigateToItemDetail(message);
}
});
}
void _navigateToItemDetail(Map<String, dynamic> message) {
final Item item = _itemForMessage(message);
// Clear away dialogs
Navigator.popUntil(context, (Route<dynamic> route) => route is PageRoute);
if (!item.route.isCurrent) {
Navigator.push(context, item.route);
}
}
@override
void initState() {
super.initState();
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
_showItemDialog(message);
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
_navigateToItemDetail(message);
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
_navigateToItemDetail(message);
},
);
_firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(
sound: true, badge: true, alert: true, provisional: true));
_firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings settings) {
print("Settings registered: $settings");
});
_firebaseMessaging.getToken().then((String token) {
assert(token != null);
setState(() {
_homeScreenText = "Push Messaging token: $token";
});
print(_homeScreenText);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Push Messaging Demo'),
),
// For testing -- simulate a message being received
floatingActionButton: FloatingActionButton(
onPressed: () => _showItemDialog(<String, dynamic>{
"data": <String, String>{
"id": "2",
"status": "out of stock",
},
}),
tooltip: 'Simulate Message',
child: const Icon(Icons.message),
),
body: Material(
child: Column(
children: <Widget>[
Center(
child: Text(_homeScreenText),
),
Row(children: <Widget>[
Expanded(
child: TextField(
controller: _topicController,
onChanged: (String v) {
setState(() {
_topicButtonsDisabled = v.isEmpty;
});
}),
),
FlatButton(
child: const Text("subscribe"),
onPressed: _topicButtonsDisabled
? null
: () {
_firebaseMessaging
.subscribeToTopic(_topicController.text);
_clearTopicText();
},
),
FlatButton(
child: const Text("unsubscribe"),
onPressed: _topicButtonsDisabled
? null
: () {
_firebaseMessaging
.unsubscribeFromTopic(_topicController.text);
_clearTopicText();
},
),
----------
])
],
),
));
}
void _clearTopicText() {
setState(() {
_topicController.text = "";
_topicButtonsDisabled = true;
});
}
}
void main() {
runApp(
MaterialApp(
home: PushMessagingExample(),
),
);
}
一切正常,但通知没有按我的意愿出现。 我需要如下通知:
它会出现大约 2-3 秒,然后如果我们忽略它,我们可以在托盘中查看它。 [1]: https://i.stack.imgur.com/vfeSd.png
但我只是在顶部栏的时钟旁边看到颤动图标,而不是这个,我必须拖动并检查托盘以检查通知。
所以,请告诉我如何在顶部显示带有声音和小横幅的通知,该通知应显示几秒钟,然后如果通知被忽略,应保留在托盘中。
你已经按照官方文档中提到的那样做了,但请相信我,官方文档是如此死板和不完整。
您需要执行几个步骤才能显示通知横幅:
来源和学分: https://github.com/FirebaseExtended/flutterfire/issues/1327#issuecomment-623399564
将以下内容添加到您的 strings.xml
。当然,您会希望为自己的应用自定义这些内容,并注意频道名称和频道描述是用户将在其通知设置中看到的内容。
<string name="notification_channel_id" translatable="false">my_unique_fcm_id</string>
<string name="notification_channel_name">The name of this notification</string>
<string name="notification_channel_desc">A description of the notification.</string>
通过将以下代码添加到您的应用程序来注册新的通知渠道。这应该尽早完成,例如,在 MainActivity.kt
(或 .java)中调用 super.onCreate 之后。
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelID = getString(R.string.notification_channel_id)
val name = getString(R.string.notification_channel_name)
val descriptionText = getString(R.string.notification_channel_desc)
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(channelID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
注意设置重要性的行,因为这是导致此频道上发送的通知显示为“注意”通知的原因。
val importance = NotificationManager.IMPORTANCE_HIGH
将以下内容添加到您的 AndroidManifest.xml 标签下。这将确保任何在其有效负载中未设置通知通道的 FCM 都将使用此通道。 (optional)
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/notification_channel_id" />
清理并重建
发送测试 FCM。它应该默认使用您新创建的频道并显示为“注意”通知。
(可选)您可以重复这些步骤,为您计划发送的每种通知类型设置更多通知渠道。只需确保每个都有一个唯一的 ID,并且在发送时将通道 ID 与通知负载捆绑在一起。 完整的文档在这里。