[译]如何在Flutter中实现本地通知——教程篇

5,011 阅读8分钟

通知提醒用户有关他们所订阅的应用程序和服务的重要信息。它们的目的是改善用户体验,推动应用程序内的参与。

当涉及到移动应用程序时,有两种类型的通知,推送通知和本地通知。在这篇文章中,我们将使用flutter
_local_notifications
包在Android和iOS平台上实现本地通知。

什么是本地通知?

使用本地通知是一种与你的用户互动的方式,可以在不使用互联网连接的情况下将他们的注意力吸引到你的应用程序上,像提醒程序和待办事项应用程序就大量使用了这种通知。它们通常是预先安排好的,当用户在应用中进行某些操作时就会发出通知。

本地通知与推送通知

本地通知和推送通知的主要区别在于,本地通知是由应用程序在本地安排的,并由同一设备发送,而推送通知是由远程服务器发送的。让我们建立一个项目,以便你能看到本地通知是如何工作的。

向Flutter应用程序添加依赖项

第一步是在你的终端运行下面的命令,将最新版本的flutter_local_notifications添加到你的pubspec.yaml 文件。

//run this command in the terminal 
$ flutter pub add flutter_local_notifications

接下来,创建一个名为notification_service.dart 的新Dart文件。你可以给它起任何你想要的文件名,但我更喜欢根据文件的功能来命名。

在Flutter中,最好的做法是将你的逻辑与你的用户界面分离开来。为了做到这一点,我们将在notification_service.dart 文件中创建一个名为NotificationService 的类。这个类将处理所有的通知逻辑,并公开创建、发送、安排和取消通知的方法。

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class NotificationService {
  //Singleton pattern
  static final NotificationService _notificationService =
      NotificationService._internal();
  factory NotificationService() {
    return _notificationService;
  }
  NotificationService._internal();

    //instance of FlutterLocalNotificationsPlugin
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = 
      FlutterLocalNotificationsPlugin();
}

让我们分析一下上面的代码中发生了什么。

我们使用了单子模式来创建NotificationService 类。此外,在第12行,我们创建了一个FlutterLocalNotificationsPlugin 的实例,它为Android和iOS平台初始化了Flutter本地通知设置。

配置特定平台的初始化设置

让我们来看看我们如何为Android和iOS平台配置初始化设置。

配置安卓初始化设置
要配置安卓初始化设置,我们需要传入一个必要的参数,那就是将显示在通知栏中的应用图标。

final AndroidInitializationSettings initializationSettingsAndroid = 
  AndroidInitializationSettings('app_icon');

现在我们需要将我们的图标作为可画资源添加到Android head项目中。下面是这样做的完整路径。

YOUR_APPLICATION_FOLDER_NAME\android\app\src\main\res\drawable\YOUR_APP_ICON.png

为iOS配置初始化设置
为iOS配置这些设置要复杂一些,因为我们必须考虑不同版本的iOS操作系统对通知的多种处理方式。

首先,在你的iOS项目的AppDelegate.swift 文件中的didFinishLaunchingWithOptions 方法中添加以下几行。

if #available(iOS 10.0, *) {
  UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}

我们的AppDelegate.swift 文件应该看起来像这样。

import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    if #available(iOS 10.0, *) {
      UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
    }
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

IOSInitializationSettings 对象接收了三个参数:requestSoundPermission,requestBadgePermission, 和requestAlertPermission 。这些参数控制向用户请求的权限。

根据你的使用情况,你可以选择将所有的通知权限设置为false ,然后在你的应用程序中的适当位置调用具有所需权限的requestIOSPermissions 方法,如下图所示。

//Initialization Settings for iOS devices 
    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
    );


 void requestIOSPermissions(
    FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin) {
  flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          IOSFlutterLocalNotificationsPlugin>()
      ?.requestPermissions(
        alert: true,
        badge: true,
        sound: true,
      );
}

创建InitializationSettings 对象

下一步是创建一个InitializationSettings 对象。这个插件用于初始化Android和iOS平台的设置。

一般来说,InitializationSettings 有三个命名的可选参数,androidiOS ,和macOS ,它们接收相应的平台初始化设置参数。

final InitializationSettings initializationSettings =
        InitializationSettings(
            android: initializationSettingsAndroid,
            iOS: initializationSettingsIOS);

在配置了特定平台的初始化设置后,我们将创建方法init ,它将包含我们所有的初始化设置逻辑,并在应用启动时从我们的main.dart 文件中调用。

 Future<void> init() async {

    //Initialization Settings for Android
    final AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');

    //Initialization Settings for iOS 
    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
    );

    //InitializationSettings for initializing settings for both platforms (Android & iOS)
    final InitializationSettings initializationSettings =
        InitializationSettings(
            android: initializationSettingsAndroid,
            iOS: initializationSettingsIOS);

    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
    );
  }

在上面的代码中,我们将特定平台的初始化设置传入InitializationSettings 对象。

下一步是调用FlutterLocalNotificationsPlugin 对象上的initialize 方法。这个方法接收两个参数,即InitializationSettings 对象和onSelectNotification 属性。

onSelectNotification 属性接收了一个回调函数,当通知被点选时,该函数将被触发。这个函数持有一个必要的参数,叫做payload ,它持有通过通知传递的任何数据。

Future selectNotification(String payload) async {
    await Navigator.push(
      context,
      MaterialPageRoute<void>(builder: (context) => SecondScreen(payload)),
    );
}

在这里,这个回调函数将触发导航到SecondScreen ,并在用户点击通知时显示与通知相关的payload

我们的init 方法现在应该看起来像这样。

 Future<void> init() async {

    //Initialization Settings for Android
    final AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');

    //Initialization Settings for iOS 
    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
    );

    //InitializationSettings for initializing settings for both platforms (Android & iOS)
    final InitializationSettings initializationSettings =
        InitializationSettings(
            android: initializationSettingsAndroid,
            iOS: initializationSettingsIOS);

    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings, 
      onSelectNotification: selectNotification
    );
  }

Future selectNotification(String payload) async {
    await Navigator.push(
      context,
      MaterialPageRoute<void>(builder: (context) => SecondScreen(payload)),
    );
}

让我们返回到我们的main.dart 文件。在main 函数中,我们将调用init 方法和requestiOSPermissions 方法,以便在应用程序在iOS设备上启动后立即向用户请求权限。

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await NotificationService().init(); // 
  await NotificationService().requestIOSPermissions(); // 
  runApp(MyApp());
}

在Flutter中显示一个通知

为了显示通知,我们需要创建一个特定平台的NotificationDetails 实例,该实例接收每个平台特有的参数。

AndroidNotificationDetails 处理安卓设备中通知的配置。它接受几个参数,如channelID,channelName,channelDescription,priority,importance ,等等。

iOSNotificationDetails 处理iOS设备中的通知配置,它接收的参数有:presentAlert,presentBadge,badgeNumber,subtitle,sound 等。

下面是AndroidNotificationDetails 实例的样子。

  AndroidNotificationDetails _androidNotificationDetails =
      AndroidNotificationDetails(
    'channel ID',
    'channel name',
    'channel description',
    playSound: true,
    priority: Priority.high,
    importance: Importance.high,
  );

下面是iOSNotificationDetails 实例的样子。

 IOSNotificationDetails _iosNotificationDetails = IOSNotificationDetails(
    presentAlert: bool?,
    presentBadge: bool?,
    presentSound: bool?,
    badgeNumber: int?
    attachments: List<IOSNotificationAttachment>?
    subtitle: String?, 
        threadIdentifier: String?
  );

现在,下一步是创建一个NotificationDetails 对象,将我们特定平台的通知细节对象作为参数。

const NotificationDetails platformChannelSpecifics = 
  NotificationDetails(
    android: _androidNotificationDetails,
    iOS: _iOSNotificationDetails);

接下来我们需要调用FlutterLocalNotificationsPluginshow 方法。show 方法负责创建推送通知,它期待一些参数,如id,title,body,notificationDetails, 和payload

id :通知的唯一标识符
title :通知的标题
body :通知信息
notificationDetails :我们传递给notificationDetails 对象的地方
payload :持有当通知被点选时通过通知传递的数据

await flutterLocalNotificationsPlugin.show(
      0,
      'Notification Title',
      'This is the Notification Body',
      platformChannelSpecifics,
      payload: 'Notification Payload',
    );

现在,让我们创建一个showNotification 方法,并将所有这些逻辑包裹在其中,然后我们可以从任何地方调用这个方法来显示通知。

class NotificationService {
  ....
  Future<void> showNotifications() async {
    await flutterLocalNotificationsPlugin.show(
      0,
      'Notification Title',
      'This is the Notification Body',
      platformChannelSpecifics,
      payload: 'Notification Payload',
    );
  }
}

在Flutter中安排一个本地通知

要安排一个通知,我们需要调用FlutterLocalNotificationsPluginzoneSchedule 方法。这个方法需要一个TZDateTime 类的实例,这个类是由timezone 包提供的。

因为flutter_local_notifications 插件已经依赖于timezone 包,所以不需要在我们的pubspec.yaml 文件中把timezone 包作为依赖项。我们只需要把它导入我们的notification_service.dart 文件并初始化它。

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

....

Future<void> init() async {
    final AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');

    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
    );

    final InitializationSettings initializationSettings =
        InitializationSettings(
            android: initializationSettingsAndroid,
            iOS: initializationSettingsIOS);

    //initialize timezone package here 
    tz.initializeTimeZones();  //  <----

    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings, 
      onSelectNotification: selectNotification
    );
}

zoneSchedule 方法接收了几个参数,包括id,title,body,scheduledDate,notificationDetails,payload,uiLocalNotificationDateInterpretation, 和androidAllowWhileIdle

scheduleDate 参数指定何时显示通知。androidAllowWhileIdle ,当设置为true ,确保计划的通知被显示,无论设备是否处于低功率模式。

await flutterLocalNotificationsPlugin.zonedSchedule(
        0,
        "Notification Title",
        "This is the Notification Body!",
        tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
        platformChannelSpecifics,
        androidAllowWhileIdle: true,
        uiLocalNotificationDateInterpretation:
            UILocalNotificationDateInterpretation.absoluteTime);

现在,让我们创建一个scheduleNotification 方法,并将所有这些逻辑包裹在其中,然后我们可以从任何地方调用这个方法来创建一个预定通知。

class NotificationService {
  ....
  Future<void> scheduleNotifications() async {
    await flutterLocalNotificationsPlugin.zonedSchedule(
        0,
        "Notification Title",
        "This is the Notification Body!",
        tz.TZDateTime.now(tz.local).add(const Duration(minutes: 5)),
        platformChannelSpecifics,
        androidAllowWhileIdle: true,
        uiLocalNotificationDateInterpretation:
            UILocalNotificationDateInterpretation.absoluteTime);
  }
}

在Flutter中取消一个本地通知

当取消一个通知时,你可以取消一个特定的通知或取消所有待定的通知。让我们来看看你如何做到这一点。

取消单个通知
要取消一个特定的通知,让我们创建一个新的方法,称为cancelNotification ,它将包含来自FlutterLocalNotificationsPlugin 对象的cancel 方法。这个方法需要一个参数,也就是通知的id

class NotificationService {
  ....
  Future<void> cancelNotifications() async {
    await flutterLocalNotificationsPlugin.cancel(NOTIFICATION_ID);
  }
}

取消所有通知
要取消所有待定的通知,让我们创建一个新的方法cancelAllNotifications ,它将包含来自FlutterLocalNotificationsPlugin 对象的cancelAll 方法。

与取消单个通知(其方法需要一个参数)不同,这个方法不需要任何参数。

class NotificationService {
  ....
  Future<void> cancelAllNotifications() async {
    await flutterLocalNotificationsPlugin.cancelAll();
  }
}

这里有一个GitHub资源库,包含了本教程的所有代码。如果你想看最终的构建,只需克隆该仓库并在你的电脑上运行。

总结

本地通知对于通知或提醒用户重要信息是非常有用的,而且可以在没有互联网连接的情况下实现。

你可以阅读flutter_local_notifications包的文档,了解你在项目中使用本地通知的其他方式。

The postImplementing local notifications in Flutterappeared first onLogRocket Blog.