Поведение, которое я вижу
В эмуляторе
- Работает на переднем плане
- Работает в фоновом режиме
- Работает в завершенном состоянии
Уведомления срабатывают точно вовремя во всех случаях.
- Работает на переднем плане
- Работает в фоновом режиме
- Ненадежно в отключенном состоянии.
- Уведомления работают только на большом расстоянии друг от друга.
- Пример:
8:15 утра и 8:30 → оба срабатывают - 8:15 и 8:16 → одно или оба не срабатывают
- Пример:
- Уведомления, запланированные очень близко друг к другу, часто пропускаются.
- Пропущенные уведомления появляются, когда устройство подключено к USB.
- Если некоторые уведомления не отображаются,
- Когда телефон подключен к USB и запущено приложение,
- Все ранее пропущенные уведомления отображаются сразу.
- Почему это работает надежно на эмуляторе, но не на реальном устройстве?
- Почему уведомления, запланированные близко друг к другу, не работают на реальных устройствах?
- Почему пропущенные уведомления внезапно срабатывают при подключении устройства к USB?
- Связано ли такое поведение с режимом Android Doze, пакетной передачей сигналов тревоги или оптимизацией заряда батареи?
- Объяснение основной причины
- Каково решение в этой ситуации?
файл службы уведомлений (инициализация и расписание)
import 'package:digital_remainder/core/core.dart';
// import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
class NotificationService {
static final FlutterLocalNotificationsPlugin notificationsPlugin =
FlutterLocalNotificationsPlugin();
//notification service intialization
static Future initialize() async {
const AndroidInitializationSettings androidSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosSettings =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
const InitializationSettings initializationSettings =
InitializationSettings(android: androidSettings, iOS: iosSettings);
await notificationsPlugin.initialize(initializationSettings);
//initialize timezone
initializeTimeZones();
//check notification, alarm permission, and bcg optimization
await NotificationPermission.askRequiredPermissions();
}
//android and ios notification details
static Future reminderNotificationDetails(
ReminderPriorityOptions priority,
) async {
final androidDetails = AndroidNotificationDetails(
'reminder_channel',
'Reminder Notifications',
channelDescription: 'Notifications for Digital Reminders',
importance: priority == ReminderPriorityOptions.high
? Importance.max
: priority == ReminderPriorityOptions.medium
? Importance.defaultImportance
: Importance.low,
priority: priority == ReminderPriorityOptions.high
? Priority.high
: priority == ReminderPriorityOptions.medium
? Priority.defaultPriority
: Priority.low,
playSound: true,
enableVibration: true,
// icon: '@drawable/ic_app_notification',
visibility: NotificationVisibility.public,
// sound: RawResourceAndroidNotificationSound("notification_sound"),
// color: AppColors.primary,
);
const iosDetails = DarwinNotificationDetails(
// sound: "notification_sound.wav",
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
return notificationDetails;
}
//schedule reminder notifications
static Future schedule({
required String id,
required String title,
required String body,
required DateTime dateTime,
required ReminderRepeatOptions repeat,
required ReminderPriorityOptions priority,
}) async {
try {
final details = await reminderNotificationDetails(priority);
final int notifId = id.hashCode;
tz.TZDateTime scheduledDate = tz.TZDateTime.from(dateTime, tz.local);
if (scheduledDate.isBefore(tz.TZDateTime.now(tz.local))) {
return;
}
// check if exact alarms are allowed on Android
// final androidPlugin = notificationsPlugin
// .resolvePlatformSpecificImplementation<
// AndroidFlutterLocalNotificationsPlugin
// >();
// final exactAllowed =
// await androidPlugin?.requestExactAlarmsPermission() ?? false;
// AndroidScheduleMode androidScheduleMode = exactAllowed
// ? AndroidScheduleMode.alarmClock
// : AndroidScheduleMode.exactAllowWhileIdle;
AndroidScheduleMode androidScheduleMode = AndroidScheduleMode.alarmClock;
// AndroidScheduleMode androidScheduleMode =
// AndroidScheduleMode.exactAllowWhileIdle;
DateTimeComponents? matchDateTimeComponents;
switch (repeat) {
case ReminderRepeatOptions.daily:
scheduledDate = tz.TZDateTime(
tz.local,
dateTime.year,
dateTime.month,
dateTime.day,
dateTime.hour,
dateTime.minute,
);
matchDateTimeComponents = DateTimeComponents.time;
break;
case ReminderRepeatOptions.weekly:
scheduledDate = tz.TZDateTime(
tz.local,
dateTime.year,
dateTime.month,
dateTime.day,
dateTime.hour,
dateTime.minute,
);
matchDateTimeComponents = DateTimeComponents.dayOfWeekAndTime;
break;
case ReminderRepeatOptions.monthly:
scheduledDate = tz.TZDateTime(
tz.local,
dateTime.year,
dateTime.month,
dateTime.day,
dateTime.hour,
dateTime.minute,
);
matchDateTimeComponents = DateTimeComponents.dayOfMonthAndTime;
break;
default:
// matchDateTimeComponents = DateTimeComponents.dateAndTime;
matchDateTimeComponents = null;
break;
}
await notificationsPlugin.zonedSchedule(
notifId,
title,
body,
scheduledDate,
details,
androidScheduleMode: androidScheduleMode,
matchDateTimeComponents: matchDateTimeComponents,
payload: scheduledDate.toString(),
);
// debugPrint('Scheduling for: ${scheduledDate.toString()}');
// debugPrint('Now: ${tz.TZDateTime.now(tz.local)}');
// debugPrint('getting the list of set schedules:.....');
// final list = await notificationsPlugin.pendingNotificationRequests();
// debugPrint('calculating length --- :.....');
// debugPrint(list.length.toString());
// debugPrint('schedule item --- :.....');
// for (var item in list) {
// debugPrint(' --- :.....');
// debugPrint(item.title.toString());
// debugPrint(item.body.toString());
// debugPrint(item.payload.toString());
// debugPrint(' --- :.....');
// }
} catch (e) {
// print("errror: $e");
}
}
//cancel specific scheduled notification
static Future cancel(String id) async {
final int notifId = id.hashCode;
await notificationsPlugin.cancel(notifId);
}
//cancel all scheduled notifications
static Future cancelAll() async {
await notificationsPlugin.cancelAll();
}
//for showing instance notification : testing
static Future showInstantNotification(
int id,
String? title,
String? body,
) async {
NotificationDetails notificationDetails = await reminderNotificationDetails(
ReminderPriorityOptions.high,
);
await notificationsPlugin.show(id, title, body, notificationDetails);
}
}
файл разрешения на уведомления:
import 'dart:io';
import 'package:digital_remainder/core/core.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';
class NotificationPermission {
static final FlutterLocalNotificationsPlugin notificationsPlugin =
NotificationService.notificationsPlugin;
static Future askRequiredPermissions() async {
await _askNotificationPermission();
await _askExactAlarmPermission();
await _askBatteryOptimizationPermission();
}
static Future _askNotificationPermission() async {
var status = await Permission.notification.status;
if (!status.isGranted) {
var request = await Permission.notification.request();
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>()
?.requestNotificationsPermission();
if (request.isGranted) {
CustomSnackbar.showToastMessage(
type: ToastType.success,
message: 'Notification Permission Granted.',
);
} else {
CustomSnackbar.showToastMessage(
type: ToastType.info,
message: 'Notification Permission Denied.',
);
}
}
}
static Future _askExactAlarmPermission() async {
if (!Platform.isAndroid) return;
final androidPlugin = notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>();
final canSchedule = await androidPlugin?.canScheduleExactNotifications();
if (canSchedule == false) {
CustomSnackbar.showToastMessage(
type: ToastType.info,
message: 'Please allow Exact Alarm for reminders to work properly.',
);
await androidPlugin?.requestExactAlarmsPermission();
}
}
static Future _askBatteryOptimizationPermission() async {
final status = await Permission.ignoreBatteryOptimizations.status;
if (!status.isGranted) {
var request = await Permission.ignoreBatteryOptimizations.request();
if (request.isGranted) {
CustomSnackbar.showToastMessage(
type: ToastType.success,
message: 'Battery Optimization Ignored.',
);
} else {
CustomSnackbar.showToastMessage(
type: ToastType.info,
message: 'Permission Denied.',
);
}
}
}
}
инициализация часового пояса:
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
void initializeTimeZones() async {
final TimezoneInfo currentTimeZone = await FlutterTimezone.getLocalTimezone();
final localLocation = currentTimeZone.identifier;
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation(localLocation));
}
помощник по планированию напоминаний:
import 'package:digital_remainder/core/core.dart';
import 'package:digital_remainder/modules/reminder/reminder.dart';
class ReminderScheduler {
static Future add(ReminderEntity entity) async {
final scheduledDateTime = DateTime(
entity.date.year,
entity.date.month,
entity.date.day,
entity.time.hour,
entity.time.minute,
);
await NotificationService.schedule(
id: entity.id!,
title: entity.title,
body: entity.description ?? 'Time for this reminder',
dateTime: scheduledDateTime,
repeat: entity.repeat,
priority: entity.priority,
);
}
static Future update(ReminderEntity entity) async {
await NotificationService.cancel(entity.id!);
if (entity.alertNotification) {
await add(entity);
}
}
static Future delete(String id) async {
await NotificationService.cancel(id);
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... t-unreliab
Мобильная версия