You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter代码中通过Firebase Messaging实现定时通知?

问题描述

我已经在Flutter项目中集成了Firebase推送通知功能,输入标题、描述并选择日期后点击按钮,Firebase会立即向设备发送通知。但我希望能根据应用内设置的具体日期和时间接收定时通知。

相关截图

  • 应用UI:应用UI截图
  • 通知截图:通知接收截图

UI代码

DateTime now = new DateTime.now();
void showDatePickerNextVaccinationDate() {
  DateTime mindate = DateTime(now.year , now.month, now.day);
  DateTime maxdate = DateTime(now.year + 12, now.month, now.day);
  showCupertinoModalPopup(
      context: context,
      builder: (BuildContext builder) {
        return Container(
          height: MediaQuery.of(context).copyWith().size.height * 0.25,
          color: Colors.white,
          child: CupertinoDatePicker(
            maximumYear: now.year + 12,
            minimumYear: now.year ,
            mode: CupertinoDatePickerMode.dateAndTime,
            initialDateTime: mindate,
            onDateTimeChanged: (value) {
              if (value != nextDate) {
                setState(() {
                  nextDate = value;
                  //calAge();
                });
              }
            },
            maximumDate: maxdate,
            minimumDate: mindate,
          ),
        );
      });
}
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          TextFormField(
            controller: title,
          ),
          TextFormField(
            controller: body,
          ),
          GestureDetector(
            onTap: showDatePickerNextVaccinationDate,
            child: SizedBox(
              width: 500,
              height: 70,
              child: Container(
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.black),
                  borderRadius: BorderRadius.circular(15),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Padding(
                      padding: EdgeInsets.only(
                        left: 3,
                        top:20,
                        bottom: 10,
                      ),
                      child: Text(
                        nextDate == null
                            ? 'Next Vaccinated Date'
                            : DateFormat('HH MMMM dd yyyy ')
                            .format(nextDate!),
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.w300,
                        ),
                      ),
                    ),
                    Icon(
                      Icons.arrow_drop_down,
                      color: Colors.grey[700],
                      size: 30.0,
                    ),
                  ],
                ),
              ),
            ),
          ),
          GestureDetector(
            onTap: () async {
              String name = "User1";
              String titleText = title.text;
              String bodyText = body.text;
              String selectedNextDate  = nextDate.toString();
              if (name != "") {
                DocumentSnapshot snap = await FirebaseFirestore.instance
                    .collection("UserTokens")
                    .doc(name)
                    .get();
                String token = snap['token'];
                print(token);
                //call sendPushMessage
                sendPushMessage(token, titleText, bodyText,selectedNextDate);
              }
            },
            child: Container(
              margin: EdgeInsets.all(20),
              height: 40,
              width: 200,
              decoration: BoxDecoration(
                  color: Colors.red,
                  borderRadius: BorderRadius.circular(20),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.redAccent.withOpacity(0.5),
                    )
                  ]),
              child: Center(
                child: Text("button"),
              ),
            ),
          )
        ],
      ),
    ),
  );
}

发送通知函数代码

void sendPushMessage(String token, String body, String title,String selectedNextDate) async {
    try {
      await http.post(Uri.parse('https://fcm.googleapis.com/fcm/send'),
          headers: <String, String>{
            'Content-Type': 'application/json',
            'Authorization': 'key=....',
          },
          body: jsonEncode<String, dynamic>{
            'priority': 'high',

            //want this to when click message open that page (payload function)
            'data': <String, dynamic>{
              'click_action': 'Test_Notification',
              'status': 'done',
              'body': '$body $selectedNextDate',
              'title': title,
              "isScheduled" : "true",
              "scheduledTime" : "2023-05-17 18:44:00"

            },
            "notification": <String, dynamic>{
              "title": title,
              "body":'$body $selectedNextDate',
              "android_channel_id": "dbfood",
          "isScheduled" : "true",
          "scheduledTime" : "2023-05-17 18:44:00"
            },
            "to": token,
          });
    } catch (e) {
      if (kDebugMode) {
        print("Error push notification");
      }
    }
  }
解决方案

Firebase Cloud Messaging(FCM)本身不支持直接发送定时推送,要实现定时通知,有两种可行方案:

方案一:Firebase Cloud Functions + Cloud Scheduler(生产级推荐)

这是服务器端调度方案,不受应用状态影响,即使应用被杀死也能按时触发:

  1. 存储定时任务:用户设置好通知时间后,将通知标题、内容、目标token、触发时间等信息存入Firebase Firestore。
  2. 部署Cloud Function:编写云函数,读取Firestore中到期且未发送的任务,调用FCM API发送通知,同时标记任务为已完成或删除。
  3. 配置Cloud Scheduler:设置定时触发器(比如每分钟执行一次),调用云函数检查并处理到期任务。

示例云函数(JavaScript):

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

exports.sendScheduledNotifications = functions.pubsub.schedule('every 1 minutes').onRun(async (context) => {
  const now = new Date();
  // 查询所有到期且未发送的任务
  const snapshot = await admin.firestore().collection('scheduledNotifications')
    .where('scheduledTime', '<=', now)
    .where('sent', '==', false)
    .get();

  const promises = [];
  snapshot.forEach(doc => {
    const data = doc.data();
    // 构造FCM消息
    const message = {
      token: data.token,
      notification: {
        title: data.title,
        body: data.body
      },
      data: data.data
    };
    // 发送通知
    promises.push(admin.messaging().send(message));
    // 标记任务已发送
    promises.push(doc.ref.update({sent: true}));
  });

  return Promise.all(promises);
});

方案二:本地定时通知(仅适用于应用后台存活场景)

如果能接受应用被杀死后任务失效的限制,可以用flutter_local_notifications插件实现本地定时:

  1. 添加依赖:在pubspec.yaml中加入flutter_local_notifications和shared_preferences。
  2. 初始化本地通知插件:
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> initLocalNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('@mipmap/ic_launcher');
  final InitializationSettings initializationSettings =
      InitializationSettings(android: initializationSettingsAndroid);
  await flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
  1. 调度本地通知:
void scheduleLocalNotification(DateTime scheduledTime, String title, String body) async {
  const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
    'dbfood',
    '疫苗提醒',
    channelDescription: '定时疫苗通知',
    importance: Importance.max,
    priority: Priority.high,
  );
  const NotificationDetails notificationDetails = NotificationDetails(android: androidDetails);

  // 计算延迟时间(需确保目标时间晚于当前时间)
  int delay = scheduledTime.difference(DateTime.now()).inMilliseconds;
  if (delay > 0) {
    await flutterLocalNotificationsPlugin.schedule(
      0, // 通知ID
      title,
      body,
      scheduledTime,
      notificationDetails,
      androidAllowWhileIdle: true, // 允许设备休眠时触发
    );
  }
}
  1. 修改按钮点击逻辑:
GestureDetector(
  onTap: () async {
    String titleText = title.text;
    String bodyText = body.text;
    if (nextDate != null && titleText.isNotEmpty && bodyText.isNotEmpty) {
      // 调用本地通知调度方法
      await scheduleLocalNotification(nextDate!, titleText, bodyText);
    }
  },
  // ... 按钮UI代码
)

注意事项

  • 服务器端方案稳定性更高,是生产环境首选;本地方案依赖应用后台存活能力,Android需处理电池优化限制,iOS定时机制有较多限制。
  • 现有代码中硬编码的scheduledTime需替换为用户选择的nextDate转换后的标准格式(如ISO8601)。

内容的提问来源于stack exchange,提问作者Dasun Dola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:42:01