Flutter:即使应用程序关闭也能推送通知

Flutter: Push notifications even if the app is closed

我已经用 flutter 构建了一个应用程序,它就像一个提醒器。
如何在应用程序关闭时向用户显示通知?

对于提醒,我会推荐 Flutter Local Notifications Plugin。它有一个强大的调度api。来自本地通知的文档:

Scheduling when notifications should appear - Periodically show a notification (interval-based) - Schedule a notification to be shown daily at a specified time - Schedule a notification to be shown weekly on a specified day and time - Ability to handle when a user has tapped on a notification when the app is the foreground, background or terminated

对于推送通知,您可以使用 Firebase Cloud Messaging one signal plugin or you can implement natively through platform-channels

编辑:即使应用已终止,您也可以根据特定条件触发通知。这可以通过后台的 运行 dart 代码来实现。引用自官方常见问题:

Can I run Dart code in the background of an Flutter app? Yes, you can run Dart code in a background process on both iOS and Android. For more information, see the Medium article Executing Dart in the Background with Flutter Plugins and Geofencing.

您可以在 flutter 中使用定时通知。

 var scheduledNotificationDateTime =
        new DateTime.now().add(new Duration(seconds: 5));
  var androidPlatformChannelSpecifics =
     new AndroidNotificationDetails('your other channel id',
    'your other channel name', 'your other channel description');
  var iOSPlatformChannelSpecifics =
   new IOSNotificationDetails();
   NotificationDetails platformChannelSpecifics = new 
  NotificationDetails(
   androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
  await flutterLocalNotificationsPlugin.schedule(
 0,
 'scheduled title',
 'scheduled body',
 scheduledNotificationDateTime,
 platformChannelSpecifics);

我也遇到过这个问题,所以这些是我的学习

在我的案例中:我能够在 App-Resume 或 App-background 状态下收到通知,但在 App-Close 状态下,我收不到通知。

在这种情况下,我们的通知正文是:

{notification: {body: null, title: null}, data: {body: hello, title: world}}

为了在应用程序关闭状态下接收通知,我们将通知更改为

{notification: {body: abc, title: abc}, data: {url: string, body: string, title: string}}

我找到了解决这个问题的方法。我们只需要在应用程序中注册本地通知插件 class.

首先创建一个class FlutterLocalNotificationPluginRegistrant,我在Kotlin中创建了这个。

class FlutterLocalNotificationPluginRegistrant {

companion object {
    fun registerWith(registry: PluginRegistry) {
        if (alreadyRegisteredWith(registry)) {
            Log.d("Local Plugin", "Already Registered");
            return
        }
        FlutterLocalNotificationsPlugin.registerWith(registry.registrarFor("com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin"))
        Log.d("Local Plugin", "Registered");
    }

    private fun alreadyRegisteredWith(registry: PluginRegistry): Boolean {
        val key = FlutterLocalNotificationPluginRegistrant::class.java.canonicalName
        if (registry.hasPlugin(key)) {
            return true
        }
        registry.registrarFor(key)
        return false
    }
}}

现在创建一个扩展 FlutterApplication 的应用程序 class 并实现 PluginRegistry.PluginRegistrantCallback.

class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {

override fun onCreate() {
    super.onCreate()
}

override fun registerWith(registry: PluginRegistry?) {
    if (registry != null) {
        FlutterLocalNotificationPluginRegistrant.registerWith(registry)
    }
}}

并在 AndroidManifest.xml

中注册应用程序 class
<application
    android:name="com.packagename.Application"/>

全部完成。现在编写一个函数来显示通知并从 Firebase 消息传递的后台处理程序方法中调用它。

    Future _showNotificationWithDefaultSound(String title, String message) async {
  var androidPlatformChannelSpecifics = AndroidNotificationDetails(
      'channel_id', 'channel_name', 'channel_description',
      importance: Importance.Max, priority: Priority.High);
  var iOSPlatformChannelSpecifics = IOSNotificationDetails();
  var platformChannelSpecifics = NotificationDetails(
      androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
   await flutterLocalNotificationsPlugin.show(
    0,
    '$title',
    '$message',
    platformChannelSpecifics,
    payload: 'Default_Sound',
  );
}

然后这样称呼它。

    Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {

  if (message['data'] != null) {
    final data = message['data'];

    final title = data['title'];
    final body = data['message'];

    await _showNotificationWithDefaultSound(title, message);
  }
  return Future<void>.value();
}

对于使用 2.2 左右最新版本的用户,只需调用 firebaseMessageInstance

FirebaseMessaging.instance.getInitialMessage().then((message) =>
      message.messageId.isNotEmpty
          ? print('we can now navigate to specific screen')
          : print('there is no new notification so default screen will be shown when application start from terminated state'));

别忘了打电话给

Navigator.push(
        context, MaterialPageRoute(builder: (context) => YourScreenName()));

当message.messageId.isNotEmpty

如果你喜欢这种方法,请点赞谢谢祝你编码愉快

如果不需要联网,可以使用这个包flutter local notification && flutter native timezone 将包添加到 pubspace.ymal 之后 将此代码添加到 android/app/src/main/AndroidManifest.xml

<activity
android:showWhenLocked="true"
android:turnScreenOn="true">

如果您在函数 didFinishLaunchingWithOptions add

中使用 swift Runner/AppDelegate.swift,也会在 ios 文件夹中打开
if #available(iOS 10.0, *) {UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate}

如果您在函数 didFinishLaunchingWithOptions add

中使用了 Object-C Runner/AppDelegate.m
 if (@available(iOS 10.0, *)) {[UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;

}

之后你应该添加 app-icon 到 drawable 文件夹 然后在文件 dart create 中导入包 import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; 并添加

class NotifyHelper {
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  String selectedNotificationPayload = '';

  final BehaviorSubject<String> selectNotificationSubject =
      BehaviorSubject<String>();
  initializeNotification() async {
    tz.initializeTimeZones();
    _configureSelectNotificationSubject();
    await _configureLocalTimeZone();
    // await requestIOSPermissions(flutterLocalNotificationsPlugin);
    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
      onDidReceiveLocalNotification: onDidReceiveLocalNotification,
    );

    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('appicon');

    final InitializationSettings initializationSettings =
        InitializationSettings(
      iOS: initializationSettingsIOS,
      android: initializationSettingsAndroid,
    );
    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onSelectNotification: (String? payload) async {
        if (payload != null) {
          debugPrint('notification payload: ' + payload);
        }
        selectNotificationSubject.add(payload!);
      },
    );
  }

  displayNotification({required String title, required String body}) async {
    print('doing test');
    var androidPlatformChannelSpecifics = const AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.max, priority: Priority.high);
    var iOSPlatformChannelSpecifics = const IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        android: androidPlatformChannelSpecifics,
        iOS: iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
      0,
      title,
      body,
      platformChannelSpecifics,
      payload: 'Default_Sound',
    );
  }

  // this is the scheduled notification
  // Task is a model class have a data item like title, desc, start time and end time
  scheduledNotification(int hour, int minutes, Task task) async {
    await flutterLocalNotificationsPlugin.zonedSchedule(
      task.id!,
      task.title,
      task.note,
      //tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
      _nextInstanceOfTenAM(hour, minutes),
      const NotificationDetails(
        android: AndroidNotificationDetails(
            'your channel id', 'your channel name', 'your channel description'),
      ),
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
      payload: '${task.title}|${task.note}|${task.startTime}|',
    );
  }

  tz.TZDateTime _nextInstanceOfTenAM(int hour, int minutes) {
    final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
    tz.TZDateTime scheduledDate =
        tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minutes);
    if (scheduledDate.isBefore(now)) {
      scheduledDate = scheduledDate.add(const Duration(days: 1));
    }
    return scheduledDate;
  }

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

  Future<void> _configureLocalTimeZone() async {
    tz.initializeTimeZones();
    final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
    tz.setLocalLocation(tz.getLocation(timeZoneName));
  }

/*   Future selectNotification(String? payload) async {
    if (payload != null) {
      //selectedNotificationPayload = "The best";
      selectNotificationSubject.add(payload);
      print('notification payload: $payload');
    } else {
      print("Notification Done");
    }
    Get.to(() => SecondScreen(selectedNotificationPayload));
  } */

//Older IOS
  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: const Text('Title'),
        content: const Text('Body'),
        actions: [
          CupertinoDialogAction(
            isDefaultAction: true,
            child: const Text('Ok'),
            onPressed: () async {
              Navigator.of(context, rootNavigator: true).pop();
              await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => Container(color: Colors.white),
                ),
              );
            },
          )
        ],
      ),
    ); 
 */
    Get.dialog( Text(body!));
  }
  //I used Get package Get here to go screen notification
  void _configureSelectNotificationSubject() {
    selectNotificationSubject.stream.listen((String payload) async {
      debugPrint('My payload is ' + payload);
      await Get.to(() => NotificationScreen(payload));
    });
  }
}

使用此 class 中的对象并调用 scheduledNotification 方法