Anonymous
Geplante Flutter-Zone-Benachrichtigungen funktionieren im Vordergrund/Hintergrund, sind jedoch im beendeten Zustand auf
Post
by Anonymous » 21 Dec 2025, 06:28
Ich arbeite an einer
Flutter-Erinnerungs-App mit geplanten lokalen Benachrichtigungen.
Verhalten, das ich sehe
Auf dem Emulator
Funktioniert im Vordergrund
Funktioniert im Hintergrund
Funktioniert im beendeten Zustand
Benachrichtigungen werden in allen Fällen pünktlich ausgelöst.
Auf echtem Android-Gerät
Funktioniert im Vordergrund
Funktioniert im Hintergrund
Unzuverlässig im beendeten Zustand
Auf realem Gerät beobachtete Muster
Benachrichtigungen funktionieren nur, wenn sie weit voneinander entfernt sind
Beispiel:
8:15 Uhr und 8:30 Uhr → beide feuern
8:15 Uhr und 8:16 Uhr → einer oder beide feuern nicht
Benachrichtigungen, die sehr nah beieinander geplant sind, werden oft übersprungen.
Verpasste Benachrichtigungen werden angezeigt, wenn das Gerät an USB angeschlossen ist
Wenn einige Benachrichtigungen nicht angezeigt werden,
Wenn das Telefon an USB angeschlossen ist und die App ausgeführt wird,
Alle zuvor verpassten Benachrichtigungen werden auf einmal angezeigt.
Was ich verstehen möchte
Warum funktioniert das zuverlässig auf dem Emulator, aber nicht auf einem echten Gerät?
Warum schlagen nahe beieinander geplante Benachrichtigungen auf echten Geräten fehl?
Warum werden verpasste Benachrichtigungen plötzlich ausgelöst, wenn das Gerät angeschlossen wird? USB?
Hängt dieses Verhalten mit dem Android Doze-Modus, der Alarm-Batching-Funktion oder der Batterieoptimierung zusammen?
Ich suche:
Erklärung der Grundursache
Was ist die Lösung für diese Situation?
Android-Manifestdatei:
Benachrichtigungsdienstdatei (Initialisierung und Zeitplan)
Code: Select all
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);
}
}
Benachrichtigungsberechtigungsdatei:
Code: Select all
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.',
);
}
}
}
}
Zeitzoneninitialisierung:
Code: Select all
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));
}
Erinnerungsplanungshelfer:
Code: Select all
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);
}
}
1766294897
Anonymous
Ich arbeite an einer [b]Flutter-Erinnerungs-App[/b] mit geplanten lokalen Benachrichtigungen. Verhalten, das ich sehe [b]Auf dem Emulator[/b] [list] [*]Funktioniert im Vordergrund [*]Funktioniert im Hintergrund [*]Funktioniert im beendeten Zustand Benachrichtigungen werden in allen Fällen pünktlich ausgelöst. [/list] [b]Auf echtem Android-Gerät[/b] [list] [*]Funktioniert im Vordergrund [*]Funktioniert im Hintergrund [*]Unzuverlässig im beendeten Zustand [/list] Auf realem Gerät beobachtete Muster [list] [*][b]Benachrichtigungen funktionieren nur, wenn sie weit voneinander entfernt sind[/b] [list] Beispiel: 8:15 Uhr und 8:30 Uhr → beide feuern [*]8:15 Uhr und 8:16 Uhr → einer oder beide feuern nicht [/list] [*]Benachrichtigungen, die sehr nah beieinander geplant sind, werden oft übersprungen. [*][b]Verpasste Benachrichtigungen werden angezeigt, wenn das Gerät an USB angeschlossen ist[/b] [list] Wenn einige Benachrichtigungen nicht angezeigt werden, [*]Wenn das Telefon an USB angeschlossen ist und die App ausgeführt wird, [*]Alle zuvor verpassten Benachrichtigungen werden auf einmal angezeigt. [/list] [/list] Was ich verstehen möchte [list] [*]Warum funktioniert das zuverlässig auf dem Emulator, aber nicht auf einem echten Gerät? [*]Warum schlagen nahe beieinander geplante Benachrichtigungen auf echten Geräten fehl? [*]Warum werden verpasste Benachrichtigungen plötzlich ausgelöst, wenn das Gerät angeschlossen wird? USB? [*]Hängt dieses Verhalten mit dem Android Doze-Modus, der Alarm-Batching-Funktion oder der Batterieoptimierung zusammen? [/list] [b]Ich suche:[/b] [list] [*]Erklärung der Grundursache [*]Was ist die Lösung für diese Situation? [/list] Android-Manifestdatei: [code] [/code] Benachrichtigungsdienstdatei (Initialisierung und Zeitplan) [code]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); } } [/code] Benachrichtigungsberechtigungsdatei: [code]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.', ); } } } } [/code] Zeitzoneninitialisierung: [code]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)); } [/code] Erinnerungsplanungshelfer: [code]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); } } [/code]