How to send daily scheudled notifications. I have instant notificaitons that does work, but scheduling notifications doesn't seem to work

1 week ago 5
ARTICLE AD BOX

Iäm having issues with sending daily notifications on android. I feel like i have tried everything. The code below is what i use, and then i initialise init in main.dart.

I see no errors, i see correct logs for the scheduling calculations (as shown at the bottom), but the notification never comes. I also pasted the uses-permissions i have in androidmanifest. Any help would be grealty appreciated.

TheinstantDebugNotification function works perfectly, its just the scheduling notifications that doesnt work

import 'dart:io'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/data/latest_all.dart' as tz_data; import 'package:timezone/timezone.dart' as tz; import 'package:flutter/foundation.dart'; class NotificationHelper { static final NotificationHelper _instance = NotificationHelper._internal(); factory NotificationHelper() => _instance; NotificationHelper._internal(); final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); // Storage Keys static const String _prefHasAsked = 'has_asked_notification_permission'; static const String _prefIsEnabled = 'is_notifications_enabled'; // 1. INITIALIZATION Future<void> init() async { // 1. Initialize Timezone Database tz_data.initializeTimeZones(); // 2. GET DEVICE TIMEZONE (Robust Fix) String timeZoneName; try { // Fetches the timezone info (might be Object or String depending on version) final dynamic localTz = await FlutterTimezone.getLocalTimezone(); // Extract the string ID safely if (localTz is String) { timeZoneName = localTz; } else { // Tries standard property names for TimezoneInfo objects // try .id first, then .name, then .identifier try { timeZoneName = localTz.id; } catch (_) { try { timeZoneName = localTz.name; } catch (__) { // If your specific version used .identifier, this catches it try { timeZoneName = localTz.identifier; } catch (___) { timeZoneName = 'UTC'; } } } } } catch (e) { if (kDebugMode) print("⚠️ Timezone Error: $e"); timeZoneName = 'UTC'; } // 3. Set Local Location try { tz.setLocalLocation(tz.getLocation(timeZoneName)); } catch (e) { tz.setLocalLocation(tz.getLocation('UTC')); } // 4. Android Settings (Matches your drawable file name) const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('launcher_icon'); // 5. iOS Settings final DarwinInitializationSettings iosSettings = DarwinInitializationSettings( requestAlertPermission: false, requestBadgePermission: false, requestSoundPermission: false, ); final InitializationSettings settings = InitializationSettings( android: androidSettings, iOS: iosSettings, ); await _notificationsPlugin.initialize( settings, onDidReceiveNotificationResponse: (details) { if (kDebugMode) log.i("Notification clicked: ${details.payload}"); }, ); // 6. RE-SCHEDULE (Safety Check) // Check if notifications were previously enabled by the user final prefs = await SharedPreferences.getInstance(); final bool isEnabled = prefs.getBool(_prefIsEnabled) ?? false; if (isEnabled) { // If they are enabled, refresh the schedule to ensure it's accurate // (e.g. correct timezone, correct icon, etc.) await _scheduleDailyReminders(); if (kDebugMode) print("🔄 Daily schedule refreshed on startup"); } if (kDebugMode) log.i("🔔 NotificationHelper Initialized. Timezone: $timeZoneName"); } // 2. CHECK IF WE SHOULD ASK (The "Once Only" Logic) // 3. ENABLE FLOW (User clicked "Yes") Future<bool> shouldAskForPermission() async { final prefs = await SharedPreferences.getInstance(); log.i(prefs.getBool(_prefHasAsked)); return !(prefs.getBool(_prefHasAsked) ?? false); } // 3. ENABLE FLOW Future<bool> enableNotifications() async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_prefHasAsked, true); if (Platform.isAndroid) { final androidPlugin = _notificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin >(); final granted = await androidPlugin?.requestNotificationsPermission(); if (granted != null && !granted) return false; } else if (Platform.isIOS) { final iosPlugin = _notificationsPlugin .resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin >(); final granted = await iosPlugin?.requestPermissions( alert: true, badge: true, sound: true, ); if (granted != true && granted != null) return false; } await prefs.setBool(_prefIsEnabled, true); await _scheduleDailyReminders(); return true; } // 4. DISABLE FLOW (User clicked "No" or turned off in settings) Future<void> disableNotifications() async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_prefHasAsked, true); // Still counts as "asked" await prefs.setBool(_prefIsEnabled, false); await _notificationsPlugin.cancelAll(); } // 5. SCHEDULE LOGIC (The 00:05 Magic) Future<void> _scheduleDailyReminders() async { // Schedule for 00:05 tomorrow/today /* await _scheduleDaily( id: 1, title: "Daily Puzzle Ready! 🧩", body: "Keep your streak alive. A new puzzle awaits.", hour: 0, minute: 5, ); */ final now = tz.TZDateTime.now(tz.local); // Schedule for 2 minutes from NOW await _scheduleDaily( id: 1, title: "Test Alarm", body: "If you see this, scheduling works!", hour: now.hour, minute: now.minute + 2, ); // Add Weekly Logic here later (id: 2) } Future<void> _scheduleDaily({ required int id, required String title, required String body, required int hour, required int minute, }) async { final now = tz.TZDateTime.now(tz.local); // Calculate next 00:05 instance var scheduledDate = tz.TZDateTime( tz.local, now.year, now.month, now.day, hour, minute, ); // If 00:05 has passed today, schedule for tomorrow if (scheduledDate.isBefore(now)) { scheduledDate = scheduledDate.add(const Duration(days: 1)); } log.i("📅 Scheduling Notification for: $scheduledDate"); await _notificationsPlugin.zonedSchedule( id, title, body, scheduledDate, NotificationDetails( android: AndroidNotificationDetails( 'daily_reminders_v2', // Channel ID 'Daily Reminders', // Channel Name channelDescription: 'Reminds you when the daily puzzle resets', importance: Importance.max, priority: Priority.high, icon: 'launcher_icon', ), iOS: DarwinNotificationDetails(), ), androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, // matchDateTimeComponents: DateTimeComponents.time, ); } Future<void> instantDebugNotification() async { await _notificationsPlugin.show( 999, 'Test Notification', 'If you see this, the instant notification', const NotificationDetails( android: AndroidNotificationDetails( 'test_channel', 'Test Channel', importance: Importance.max, priority: Priority.high, icon: 'launcher_icon', // <--- Matches your file ), ), ); } }

💡 📅 Scheduling Notification for: 2026-01-24 11:38:00.000+0100

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
Read Entire Article