Зонированные запланированные уведомления Flutter работают на переднем/фоновом режиме, но ненадежны в завершенном состоянAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Зонированные запланированные уведомления Flutter работают на переднем/фоновом режиме, но ненадежны в завершенном состоян

Сообщение Anonymous »

Я работаю над приложением напоминаний Flutter, используя запланированные локальные уведомления.
Поведение, которое я вижу
В эмуляторе
  • Работает на переднем плане
  • Работает в фоновом режиме
  • Работает в завершенном состоянии

    Уведомления срабатывают точно вовремя во всех случаях.
На реальном устройстве Android
  • Работает на переднем плане
  • Работает в фоновом режиме
  • Ненадежно в отключенном состоянии.
Схемы, наблюдаемые на реальном устройстве.
  • Уведомления работают только на большом расстоянии друг от друга.
    • Пример:

      8:15 утра и 8:30 → оба срабатывают
    • 8:15 и 8:16 → одно или оба не срабатывают
  • Уведомления, запланированные очень близко друг к другу, часто пропускаются.
  • Пропущенные уведомления появляются, когда устройство подключено к USB.
    • Если некоторые уведомления не отображаются,
    • Когда телефон подключен к USB и запущено приложение,
    • Все ранее пропущенные уведомления отображаются сразу.
Что я хочу понять
  • Почему это работает надежно на эмуляторе, но не на реальном устройстве?
  • Почему уведомления, запланированные близко друг к другу, не работают на реальных устройствах?
  • Почему пропущенные уведомления внезапно срабатывают при подключении устройства к USB?
  • Связано ли такое поведение с режимом Android Doze, пакетной передачей сигналов тревоги или оптимизацией заряда батареи?
Я ищу:
  • Объяснение основной причины
  • Каково решение в этой ситуации?
Файл манифеста Android:











































файл службы уведомлений (инициализация и расписание)
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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»