Flutter通知管理插件awesome_notifications的使用
Flutter通知管理插件awesome_notifications的使用
简介
awesome_notifications
是一个用于Flutter应用的通知管理插件,它支持创建和自定义本地和推送通知。通过这个插件,你可以轻松地为你的应用添加丰富的通知功能,并且能够接收实时事件,确保不会错过用户的任何交互。
主要特性
- 创建自定义通知:轻松创建和自定义本地及推送通知。
- 用户参与:通过带有图片、声音、表情符号、按钮等不同布局的通知保持用户活跃。
- 实时事件:在Flutter代码级别接收关于创建、显示、关闭或点击的通知事件。
- 高度可定制:提供多种定制选项,包括翻译,以满足特定需求。
- 计划通知:以秒精度重复或在特定时间安排通知,保持用户更新。
- 可靠性能:在任何应用程序生命周期内都能收到通知。
安装与配置
添加依赖
首先,在 pubspec.yaml
文件中添加 awesome_notifications
作为依赖项:
dependencies:
awesome_notifications: ^0.10.0
然后运行以下命令来获取包:
flutter pub get
初始化插件
在 main.dart
文件中初始化插件:
import 'package:awesome_notifications/awesome_notifications.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化 Awesome Notifications 插件
await AwesomeNotifications().initialize(
null, // 设置图标为null时将使用默认的应用图标
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Notification channel for basic tests',
defaultColor: Color(0xFF9D50DD),
ledColor: Colors.white,
),
],
debug: true,
);
runApp(MyApp());
}
Android配置
修改 build.gradle
文件中的 minSdkVersion
和 compileSdkVersion
:
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 23
targetSdkVersion 34
...
}
...
}
在 AndroidManifest.xml
中添加必要的权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application>
...
</application>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</manifest>
对于所有包含 <intent-filter>
的组件(如 <activity>
、<service>
等),添加 android:exported="true"
属性。
iOS配置
修改 Podfile
文件以适应Awesome Notifications的需求:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
require awesome_pod_file
update_awesome_pod_build_settings(installer)
end
awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
require awesome_pod_file
update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root)
创建通知
在任何地方创建一个新的通知:
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: -1, // 使用随机ID
channelKey: 'basic_channel',
title: 'Hello World!',
body: 'This is my first notification!'
)
);
示例代码
以下是完整的示例代码,展示了如何设置和使用 awesome_notifications
插件。
import 'dart:async';
import 'dart:isolate';
import 'dart:ui';
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:palette_generator/palette_generator.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await NotificationController.initializeLocalNotifications();
await NotificationController.initializeIsolateReceivePort();
runApp(const MyApp());
}
class NotificationController {
static ReceivedAction? initialAction;
static Future<void> initializeLocalNotifications() async {
await AwesomeNotifications().initialize(
null,
[
NotificationChannel(
channelKey: 'alerts',
channelName: 'Alerts',
channelDescription: 'Notification tests as alerts',
playSound: true,
onlyAlertOnce: true,
groupAlertBehavior: GroupAlertBehavior.Children,
importance: NotificationImportance.High,
defaultPrivacy: NotificationPrivacy.Private,
defaultColor: Colors.deepPurple,
ledColor: Colors.deepPurple,
),
],
debug: true,
);
initialAction = await AwesomeNotifications()
.getInitialNotificationAction(removeFromActionEvents: false);
}
static ReceivePort? receivePort;
static Future<void> initializeIsolateReceivePort() async {
receivePort = ReceivePort('Notification action port in main isolate')
..listen((silentData) => onActionReceivedImplementationMethod(silentData));
IsolateNameServer.registerPortWithName(
receivePort!.sendPort, 'notification_action_port');
}
static Future<void> startListeningNotificationEvents() async {
AwesomeNotifications().setListeners(onActionReceivedMethod: onActionReceivedMethod);
}
@pragma('vm:entry-point')
static Future<void> onActionReceivedMethod(ReceivedAction receivedAction) async {
if (receivedAction.actionType == ActionType.SilentAction ||
receivedAction.actionType == ActionType.SilentBackgroundAction) {
print('Message sent via notification input: "${receivedAction.buttonKeyInput}"');
await executeLongTaskInBackground();
} else {
if (receivePort == null) {
SendPort? sendPort = IsolateNameServer.lookupPortByName('notification_action_port');
if (sendPort != null) {
sendPort.send(receivedAction);
return;
}
}
return onActionReceivedImplementationMethod(receivedAction);
}
}
static Future<void> onActionReceivedImplementationMethod(ReceivedAction receivedAction) async {
MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil(
'/notification-page',
(route) => (route.settings.name != '/notification-page') || route.isFirst,
arguments: receivedAction,
);
}
static Future<bool> displayNotificationRationale() async {
bool userAuthorized = false;
BuildContext context = MyApp.navigatorKey.currentContext!;
await showDialog(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: Text('Get Notified!', style: Theme.of(context).textTheme.titleLarge),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/animated-bell.gif',
height: MediaQuery.of(context).size.height * 0.3,
fit: BoxFit.fitWidth,
),
const SizedBox(height: 20),
const Text('Allow Awesome Notifications to send you beautiful notifications!'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text('Deny', style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Colors.red)),
),
TextButton(
onPressed: () async {
userAuthorized = true;
Navigator.of(ctx).pop();
},
child: Text('Allow', style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Colors.deepPurple)),
),
],
);
},
);
return userAuthorized && await AwesomeNotifications().requestPermissionToSendNotifications();
}
static Future<void> executeLongTaskInBackground() async {
print("starting long task");
await Future.delayed(const Duration(seconds: 4));
final url = Uri.parse("http://google.com");
final re = await http.get(url);
print(re.body);
print("long task done");
}
static Future<void> createNewNotification() async {
bool isAllowed = await AwesomeNotifications().isNotificationAllowed();
if (!isAllowed) isAllowed = await displayNotificationRationale();
if (!isAllowed) return;
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: -1,
channelKey: 'alerts',
title: 'Huston! The eagle has landed!',
body: "A small step for a man, but a giant leap to Flutter's community!",
bigPicture: 'https://storage.googleapis.com/cms-storage-bucket/d406c736e7c4c57f5f61.png',
largeIcon: 'https://storage.googleapis.com/cms-storage-bucket/0dbfcc7a59cd1cf16282.png',
notificationLayout: NotificationLayout.BigPicture,
payload: {'notificationId': '1234567890'},
),
actionButtons: [
NotificationActionButton(key: 'REDIRECT', label: 'Redirect'),
NotificationActionButton(key: 'REPLY', label: 'Reply Message', requireInputText: true, actionType: ActionType.SilentAction),
NotificationActionButton(key: 'DISMISS', label: 'Dismiss', actionType: ActionType.DismissAction, isDangerousOption: true),
],
);
}
static Future<void> scheduleNewNotification() async {
bool isAllowed = await AwesomeNotifications().isNotificationAllowed();
if (!isAllowed) isAllowed = await displayNotificationRationale();
if (!isAllowed) return;
await myNotifyScheduleInHours(
title: 'test',
msg: 'test message',
heroThumbUrl: 'https://storage.googleapis.com/cms-storage-bucket/d406c736e7c4c57f5f61.png',
hoursFromNow: 5,
username: 'test user',
repeatNotif: false,
);
}
static Future<void> resetBadgeCounter() async {
await AwesomeNotifications().resetGlobalBadge();
}
static Future<void> cancelNotifications() async {
await AwesomeNotifications().cancelAll();
}
}
Future<void> myNotifyScheduleInHours({
required int hoursFromNow,
required String heroThumbUrl,
required String username,
required String title,
required String msg,
bool repeatNotif = false,
}) async {
var nowDate = DateTime.now().add(Duration(hours: hoursFromNow, seconds: 5));
await AwesomeNotifications().createNotification(
schedule: NotificationCalendar(
hour: nowDate.hour,
minute: nowDate.minute,
second: nowDate.second,
repeats: repeatNotif,
),
content: NotificationContent(
id: -1,
channelKey: 'basic_channel',
title: '${Emojis.food_bowl_with_spoon} $title',
body: '$username, $msg',
bigPicture: heroThumbUrl,
notificationLayout: NotificationLayout.BigPicture,
color: Colors.black,
backgroundColor: Colors.black,
payload: {'actPag': 'myAct', 'actType': 'food', 'username': username},
),
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static Color mainColor = const Color(0xFF9D50DD);
@override
State<MyApp> createState() => _AppState();
}
class _AppState extends State<MyApp> {
static const String routeHome = '/', routeNotification = '/notification-page';
@override
void initState() {
NotificationController.startListeningNotificationEvents();
super.initState();
}
List<Route<dynamic>> onGenerateInitialRoutes(String initialRouteName) {
List<Route<dynamic>> pageStack = [];
pageStack.add(MaterialPageRoute(builder: (_) => const MyHomePage(title: 'Awesome Notifications Example App')));
if (initialRouteName == routeNotification && NotificationController.initialAction != null) {
pageStack.add(MaterialPageRoute(builder: (_) => NotificationPage(receivedAction: NotificationController.initialAction!)));
}
return pageStack;
}
Route<dynamic>? onGenerateRoute(RouteSettings settings) {
switch (settings.name) {
case routeHome:
return MaterialPageRoute(builder: (_) => const MyHomePage(title: 'Awesome Notifications Example App'));
case routeNotification:
ReceivedAction receivedAction = settings.arguments as ReceivedAction;
return MaterialPageRoute(builder: (_) => NotificationPage(receivedAction: receivedAction));
}
return null;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Awesome Notifications - Simple Example',
navigatorKey: MyApp.navigatorKey,
onGenerateInitialRoutes: onGenerateInitialRoutes,
onGenerateRoute: onGenerateRoute,
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('Push the buttons below to create new notifications'),
],
),
),
floatingActionButton: Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
heroTag: '1',
onPressed: () => NotificationController.createNewNotification(),
tooltip: 'Create New notification',
child: const Icon(Icons.outgoing_mail),
),
const SizedBox(width: 10),
FloatingActionButton(
heroTag: '2',
onPressed: () => NotificationController.scheduleNewNotification(),
tooltip: 'Schedule New notification',
child: const Icon(Icons.access_time_outlined),
),
const SizedBox(width: 10),
FloatingActionButton(
heroTag: '3',
onPressed: () => NotificationController.resetBadgeCounter(),
tooltip: 'Reset badge counter',
child: const Icon(Icons.exposure_zero),
),
const SizedBox(width: 10),
FloatingActionButton(
heroTag: '4',
onPressed: () => NotificationController.cancelNotifications(),
tooltip: 'Cancel all notifications',
child: const Icon(Icons.delete_forever),
),
],
),
),
);
}
}
class NotificationPage extends StatefulWidget {
const NotificationPage({Key? key, required this.receivedAction}) : super(key: key);
final ReceivedAction receivedAction;
@override
NotificationPageState createState() => NotificationPageState();
}
class NotificationPageState extends State<NotificationPage> {
bool get hasTitle => widget.receivedAction.title?.isNotEmpty ?? false;
bool get hasBody => widget.receivedAction.body?.isNotEmpty ?? false;
bool get hasLargeIcon => widget.receivedAction.largeIconImage != null;
bool get hasBigPicture => widget.receivedAction.bigPictureImage != null;
double bigPictureSize = 0.0;
double largeIconSize = 0.0;
bool isTotallyCollapsed = false;
bool bigPictureIsPredominantlyWhite = true;
ScrollController scrollController = ScrollController();
Future<bool> isImagePredominantlyWhite(ImageProvider imageProvider) async {
final paletteGenerator = await PaletteGenerator.fromImageProvider(imageProvider);
final dominantColor = paletteGenerator.dominantColor?.color ?? Colors.transparent;
return dominantColor.computeLuminance() > 0.5;
}
@override
void initState() {
super.initState();
scrollController.addListener(_scrollListener);
if (hasBigPicture) {
isImagePredominantlyWhite(widget.receivedAction.bigPictureImage!)
.then((isPredominantlyWhite) => setState(() {
bigPictureIsPredominantlyWhite = isPredominantlyWhite;
}));
}
}
void _scrollListener() {
bool pastScrollLimit = scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 240;
if (!hasBigPicture) {
isTotallyCollapsed = true;
return;
}
if (isTotallyCollapsed) {
if (!pastScrollLimit) {
setState(() {
isTotallyCollapsed = false;
});
}
} else {
if (pastScrollLimit) {
setState(() {
isTotallyCollapsed = true;
});
}
}
}
@override
Widget build(BuildContext context) {
bigPictureSize = MediaQuery.of(context).size.height * .4;
largeIconSize =
MediaQuery.of(context).size.height * (hasBigPicture ? .16 : .2);
if (!hasBigPicture) {
isTotallyCollapsed = true;
}
return Scaffold(
body: CustomScrollView(
controller: scrollController,
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
elevation: 0,
centerTitle: true,
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(
Icons.arrow_back_ios_rounded,
color: isTotallyCollapsed || bigPictureIsPredominantlyWhite
? Colors.black
: Colors.white,
),
),
systemOverlayStyle:
isTotallyCollapsed || bigPictureIsPredominantlyWhite
? SystemUiOverlayStyle.dark
: SystemUiOverlayStyle.light,
expandedHeight: hasBigPicture
? bigPictureSize + (hasLargeIcon ? 40 : 0)
: (hasLargeIcon)
? largeIconSize + 10
: MediaQuery.of(context).padding.top + 28,
backgroundColor: Colors.transparent,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
stretchModes: const [StretchMode.zoomBackground],
centerTitle: true,
expandedTitleScale: 1,
collapseMode: CollapseMode.pin,
title: (!hasLargeIcon)
? null
: Stack(children: [
Positioned(
bottom: 0,
left: 16,
right: 16,
child: Row(
mainAxisAlignment: hasBigPicture
? MainAxisAlignment.start
: MainAxisAlignment.center,
children: [
SizedBox(
height: largeIconSize,
width: largeIconSize,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(largeIconSize)),
child: FadeInImage(
placeholder: const NetworkImage(
'https://cdn.syncfusion.com/content/images/common/placeholder.gif'),
image: widget.receivedAction.largeIconImage!,
fit: BoxFit.cover,
),
),
),
],
),
),
]),
background: hasBigPicture
? Padding(
padding: EdgeInsets.only(bottom: hasLargeIcon ? 60 : 20),
child: FadeInImage(
placeholder: const NetworkImage(
'https://cdn.syncfusion.com/content/images/common/placeholder.gif'),
height: bigPictureSize,
width: MediaQuery.of(context).size.width,
image: widget.receivedAction.bigPictureImage!,
fit: BoxFit.cover,
),
)
: null,
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Padding(
padding: const EdgeInsets.only(bottom: 20.0, left: 20, right: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(children: [
if (hasTitle)
TextSpan(
text: widget.receivedAction.title!,
style: Theme.of(context).textTheme.titleLarge,
),
if (hasBody)
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(top: hasTitle ? 16.0 : 0.0),
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Text(
widget.receivedAction.bodyWithoutHtml ??
'',
style: Theme.of(context).textTheme.bodyMedium)),
),
),
]),
),
],
),
),
Container(
color: Colors.black12,
padding: const EdgeInsets.all(20),
width: MediaQuery.of(context).size.width,
child: Text(widget.receivedAction.toString()),
),
],
),
),
],
),
);
}
}
这段代码展示了如何在Flutter项目中集成和使用 awesome_notifications
插件来创建和管理通知。通过这些步骤,你可以快速上手并实现丰富的通知功能。
更多关于Flutter通知管理插件awesome_notifications的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter通知管理插件awesome_notifications的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用awesome_notifications
插件进行通知管理的代码示例。这个插件提供了丰富的功能来管理本地和远程通知。
1. 添加依赖
首先,你需要在pubspec.yaml
文件中添加awesome_notifications
依赖:
dependencies:
flutter:
sdk: flutter
awesome_notifications: ^0.6.21+1 # 请检查最新版本号并更新
2. 初始化插件
在你的main.dart
文件中,你需要初始化AwesomeNotifications
插件:
import 'package:flutter/material.dart';
import 'package:awesome_notifications/awesome_notifications.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
AwesomeNotifications().initialize(
// 设置初始化参数
'YOUR_APP_KEY', // 应用唯一标识符
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Channel for basic notifications',
importance: NotificationImportance.High,
),
],
debug: true, // 开发模式下设置为true
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
3. 发送通知
接下来,你可以在应用的某个地方发送通知。例如,在一个按钮点击事件中:
import 'package:flutter/material.dart';
import 'package:awesome_notifications/awesome_notifications.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Awesome Notifications Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
AwesomeNotifications().createNotification(
content: NotificationContent(
id: 10,
channelKey: 'basic_channel',
title: 'Hello World',
body: 'This is a test notification',
),
);
},
child: Text('Send Notification'),
),
),
);
}
}
4. 处理通知点击事件
为了处理用户点击通知时的事件,你需要设置一个通知点击处理函数。你可以在main.dart
中通过AwesomeNotifications().notificationOpenedHandler
来实现:
void main() {
WidgetsFlutterBinding.ensureInitialized();
AwesomeNotifications().initialize(
'YOUR_APP_KEY',
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Channel for basic notifications',
importance: NotificationImportance.High,
),
],
debug: true,
);
// 设置通知点击处理函数
AwesomeNotifications().notificationOpenedHandler = (ReceivedNotification receivedNotification) async {
if (receivedNotification.payload != null) {
// 处理通知负载数据
final dynamic data = receivedNotification.payload;
print('Notification payload: $data');
}
// 示例:导航到特定页面
if (receivedNotification.id == 10) {
// 这里可以添加导航逻辑,比如使用Navigator.pushNamed等
print('Notification ID 10 clicked!');
}
};
runApp(MyApp());
}
5. 配置Android和iOS权限
确保在AndroidManifest.xml
和Info.plist
文件中配置了必要的权限和设置,以便应用可以发送和接收通知。
- Android: 在
AndroidManifest.xml
中添加必要的权限和服务声明。 - iOS: 在
Info.plist
中添加通知权限请求。
注意事项
- 确保在发送通知前用户已经授予了通知权限。
- 在真实应用中,避免硬编码通知ID和通道键,最好使用常量来管理。
- 在生产环境中,将
debug
参数设置为false
。
这个示例展示了如何在Flutter应用中使用awesome_notifications
插件来发送和处理本地通知。根据你的具体需求,你可以进一步定制通知的内容和行为。