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

Flutter iOS课程提醒通知实现求助:flutter_local_notifications未生效

Flutter iOS课程表应用课前5分钟通知实现方案

你的代码已完成课程倒计时和日程展示功能,但未实现flutter_local_notifications的初始化、权限申请及通知调度逻辑,这是通知无法工作的核心原因。以下是适配iOS平台的完整解决方案:

步骤1:配置依赖与iOS权限

  • 在pubspec.yaml中确保依赖最新稳定版插件:
dependencies:
  flutter:
    sdk: flutter
  flutter_local_notifications: ^16.1.0 # 请使用最新版本
  • 在iOS项目的Info.plist中添加通知权限描述:
<key>NSNotificationAlertUsageDescription</key>
<string>课程开始前5分钟推送提醒</string>
<key>NSNotificationSoundUsageDescription</key>
<string>通知提醒声音</string>
<key>NSNotificationBadgeUsageDescription</key>
<string>通知角标显示</string>

步骤2:完整代码实现

以下是整合通知功能的完整代码,关键部分已添加注释:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

void main() {
  runApp(const MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Timer _timer;
  String _timeRemaining = '';
  // 通知插件实例
  late FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin;
  // 记录已发送的通知ID,避免重复推送
  Set<int> _sentNotificationIds = {};

  @override
  void initState() {
    super.initState();
    _initNotifications();
    _startTimer();
  }

  @override
  void dispose() {
    _timer.cancel();
    super.dispose();
  }

  // 初始化通知插件并请求iOS权限
  void _initNotifications() {
    _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    const DarwinInitializationSettings initializationSettingsIOS =
        DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );
    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );
    _flutterLocalNotificationsPlugin.initialize(initializationSettings);
  }

  void _startTimer() {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        _timeRemaining = _getTimeRemaining();
      });
      // 每次计时检查是否需要发送通知
      _checkAndSendNotification();
    });
  }

  // 检查是否到达课程前5分钟,发送通知
  void _checkAndSendNotification() {
    DateTime now = DateTime.now();
    DateTime nextLesson = _getNextLessonTime();
    Duration difference = nextLesson.difference(now);
    // 当剩余时间在4分50秒到5分10秒之间,且未发送过该课程的通知
    if (difference.inSeconds >= 290 && difference.inSeconds <= 310) {
      int notificationId = nextLesson.millisecondsSinceEpoch ~/ 1000;
      if (!_sentNotificationIds.contains(notificationId)) {
        _sendNotification(notificationId, _getNextLessonName(nextLesson));
        _sentNotificationIds.add(notificationId);
      }
    }
    // 课程结束后移除已发送的通知ID,避免影响次日同课程的提醒
    if (difference.isNegative) {
      _sentNotificationIds.clear();
    }
  }

  // 发送通知
  Future<void> _sendNotification(int id, String lessonName) async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'lesson_reminder_channel',
      '课程提醒',
      channelDescription: '课程开始前5分钟推送提醒',
      importance: Importance.high,
      priority: Priority.high,
    );
    const DarwinNotificationDetails iosNotificationDetails =
        DarwinNotificationDetails();
    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
      iOS: iosNotificationDetails,
    );
    await _flutterLocalNotificationsPlugin.show(
      id,
      '课程即将开始',
      '$lessonName 还有5分钟开始,请做好准备!',
      notificationDetails,
    );
  }

  String _getTimeRemaining() {
    DateTime now = DateTime.now();
    DateTime tomorrow = DateTime(now.year, now.month, now.day + 1);
    DateTime nextLesson = _getNextLessonTime();
    Duration difference = nextLesson.difference(now);
    if (difference.isNegative) {
      nextLesson = _getNextLessonTime();
      difference = nextLesson.difference(now);
    }
    int seconds = difference.inSeconds % 60;
    int minutes = (difference.inMinutes % 60);
    int hours = difference.inHours % 24;
    String nextLessonName = _getNextLessonName(nextLesson);
    String timeRemaining;
    if (now.weekday == DateTime.sunday) {
      timeRemaining = '今天不上课。';
    } else if (tomorrow.weekday == DateTime.sunday) {
      timeRemaining = '明天不上课。';
    } else if (now.weekday == DateTime.saturday && now.hour >= 12) {
      timeRemaining = '今日课程已结束。';
    } else if (now.hour >= 14) {
      timeRemaining = '今日课程已结束。';
    } else if (difference.inHours >= 24) {
      timeRemaining = '下节课\n $nextLessonName 将于 ${nextLesson.day}/${nextLesson.month}/${nextLesson.year},还有 ${difference.inDays} 天。';
    } else if (now.hour >= nextLesson.hour && now.minute >= nextLesson.minute) {
      nextLesson = _getNextLessonTime();
      difference = nextLesson.difference(now);
      seconds = difference.inSeconds % 60;
      minutes = (difference.inMinutes % 60);
      hours = difference.inHours % 24;
      nextLessonName = _getNextLessonName(nextLesson);
      timeRemaining = '下节课\n $nextLessonName 还有 ${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}。';
    } else {
      timeRemaining = '下节课\n $nextLessonName 还有 ${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}。';
    }
    return timeRemaining;
  }

  DateTime _getNextLessonTime() {
    DateTime now = DateTime.now();
    DateTime nextLesson;
    if (now.weekday == DateTime.saturday) {
      nextLesson = DateTime(now.year, now.month, now.day, 10, 0);
      // 如果周六10点已过,返回下周一8点
      if (now.isAfter(nextLesson)) {
        nextLesson = nextLesson.add(const Duration(days: 2));
      }
    } else {
      nextLesson = DateTime(now.year, now.month, now.day, 8, 0);
      if (now.hour >= 14) {
        nextLesson = nextLesson.add(const Duration(days: 1));
        // 如果次日是周日,跳转到周一
        if (nextLesson.weekday == DateTime.sunday) {
          nextLesson = nextLesson.add(const Duration(days: 1));
        }
      } else {
        // 当天未到14点,但8点已过,获取下一节课程的时间
        nextLesson = _getNextLessonOfDay(now);
      }
    }
    return nextLesson;
  }

  // 获取当天的下一节课程时间(原逻辑仅返回8点,这里补充完整课程时间判断)
  DateTime _getNextLessonOfDay(DateTime now) {
    List<DateTime> todayLessons = [];
    switch (now.weekday) {
      case DateTime.monday:
        todayLessons = [
          DateTime(now.year, now.month, now.day, 8, 0),
          DateTime(now.year, now.month, now.day, 9, 0),
          DateTime(now.year, now.month, now.day, 10, 0),
          DateTime(now.year, now.month, now.day, 12, 0),
        ];
        break;
      case DateTime.tuesday:
        todayLessons = [
          DateTime(now.year, now.month, now.day, 8, 0),
          DateTime(now.year, now.month, now.day, 9, 0),
          DateTime(now.year, now.month, now.day, 10, 0),
          DateTime(now.year, now.month, now.day, 11, 0),
          DateTime(now.year, now.month, now.day, 12, 0),
          DateTime(now.year, now.month, now.day, 13, 0),
        ];
        break;
      case DateTime.wednesday:
        todayLessons = [
          DateTime(now.year, now.month, now.day, 8, 0),
          DateTime(now.year, now.month, now.day, 9, 0),
          DateTime(now.year, now.month, now.day, 10, 0),
          DateTime(now.year, now.month, now.day, 11, 0),
          DateTime(now.year, now.month, now.day, 12, 0),
          DateTime(now.year, now.month, now.day, 13, 0),
        ];
        break;
      case DateTime.thursday:
        todayLessons = [
          DateTime(now.year, now.month, now.day, 8, 0),
          DateTime(now.year, now.month, now.day, 9, 0),
          DateTime(now.year, now.month, now.day, 10, 0),
          DateTime(now.year, now.month, now.day, 11, 0),
          DateTime(now.year, now.month, now.day, 12, 0),
          DateTime(now.year, now.month, now.day, 13, 0),
        ];
        break;
      case DateTime.friday:
        todayLessons = [
          DateTime(now.year, now.month, now.day, 8, 0),
          DateTime(now.year, now.month, now.day, 9, 0),
          DateTime(now.year, now.month, now.day, 10, 0),
          DateTime(now.year, now.month, now.day, 11, 0),
        ];
        break;
      default:
        todayLessons = [DateTime(now.year, now.month, now.day, 8, 0)];
    }
    // 找到第一个未过去的课程时间
    for (var lessonTime in todayLessons) {
      if (now.isBefore(lessonTime)) {
        return lessonTime;
      }
    }
    // 所有课程已过,返回次日8点(已处理周日情况)
    DateTime nextDay = now.add(const Duration(days: 1));
    if (nextDay.weekday == DateTime.sunday) {
      nextDay = nextDay.add(const Duration(days: 1));
    }
    return DateTime(nextDay.year, nextDay.month, nextDay.day, 8, 0);
  }

  String _getNextLessonName(DateTime nextLesson) {
    String nextLessonName = '';
    if (nextLesson.weekday == DateTime.monday) {
      // 根据课程时间匹配课程名称
      if (nextLesson.hour == 8) {
        nextLessonName = 'Telecomunicazioni';
      } else if (nextLesson.hour == 9) {
        nextLessonName = 'Matematica';
      } else if (nextLesson.hour == 10) {
        nextLessonName = 'Italiano';
      } else if (nextLesson.hour == 12) {
        nextLessonName = 'Inglese';
      }
    } else if (nextLesson.weekday == DateTime.tuesday) {
      if (nextLesson.hour == 8) {
        nextLessonName = 'Italiano';
      } else if (nextLesson.hour >=9 && nextLesson.hour <=10) {
        nextLessonName = 'Sistemi';
      } else if (nextLesson.hour >=11 && nextLesson.hour <=13) {
        nextLessonName = 'Informatica';
      }
    } else if (nextLesson.weekday == DateTime.wednesday) {
      if (nextLesson.hour == 8) {
        nextLessonName = 'Italiano';
      } else if (nextLesson.hour ==9) {
        nextLessonName = 'Storia';
      } else if (nextLesson.hour ==10) {
        nextLessonName = 'Informatica';
      } else if (nextLesson.hour ==11) {
        nextLessonName = 'Educazione Fisica';
      } else if (nextLesson.hour ==12) {
        nextLessonName = 'Matematica';
      } else if (nextLesson.hour ==13) {
        nextLessonName = 'Inglese';
      }
    } else if (nextLesson.weekday == DateTime.thursday) {
      if (nextLesson.hour ==8) {
        nextLessonName = 'Sistemi';
      } else if (nextLesson.hour ==9) {
        nextLessonName = 'Matematica';
      } else if (nextLesson.hour ==10) {
        nextLessonName = 'Storia';
      } else if (nextLesson.hour ==11) {
        nextLessonName = 'Informatica';
      } else if (nextLesson.hour ==12) {
        nextLessonName = 'Inglese';
      } else if (nextLesson.hour ==13) {
        nextLessonName = 'T.P.I.';
      }
    } else if (nextLesson.weekday == DateTime.friday) {
      if (nextLesson.hour ==8) {
        nextLessonName = 'Matematica';
      } else if (nextLesson.hour ==9) {
        nextLessonName = 'Sistemi';
      } else if (nextLesson.hour ==10) {
        nextLessonName = 'Informatica';
      } else if (nextLesson.hour ==11) {
        nextLessonName = 'Educazione Fisica';
      }
    } else if (nextLesson.weekday == DateTime.saturday) {
      if (nextLesson.hour >=8 && nextLesson.hour <=9) {
        nextLessonName = 'Telecomunicazioni';
      } else if (nextLesson.hour >=10 && nextLesson.hour <=11) {
        nextLessonName = 'T.P.I.';
      }
    }
    return nextLessonName;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '课程表',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('课程表'),
          centerTitle: true,
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Center(
                child: Text(
                  _timeRemaining,
                  style: const TextStyle(fontSize: 20),
                  textAlign: TextAlign.center,
                ),
              ),
              const SizedBox(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  _buildDayButton('周一', context),
                  _buildDayButton('周二', context),
                ],
              ),
              const SizedBox(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  _buildDayButton('周三', context),
                  _buildDayButton('周四', context),
                ],
              ),
              const SizedBox(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  _buildDayButton('周五', context),
                  _buildDayButton('周六', context),
                ],
              ),
              const SizedBox(height: 20),
              const Text(
                '程序由Skerdi Velo开发',
                style: TextStyle(fontSize: 12),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildDayButton(String day, BuildContext context) {
    String schedule = '';
    switch (day) {
      case '周一':
        schedule = '08:00-09:00 Telecomunicazioni\n09:00-10:00 Matematica\n10:00-12:00 Italiano\n12:00-13:
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 23:35:19