7.6 KiB
Plugin feedback: Android duplicate reminder notification on first-time setup
Date: 2026-02-18
Generated: 2026-02-18 17:47:06 PST
Target repo: daily-notification-plugin (local copy at daily-notification-plugin_test)
Consuming app: crowd-funder-for-time-pwa (TimeSafari)
Platform: Android
Summary
When the user sets a Reminder Notification for the first time (toggle on → set message and time in PushNotificationPermission), two notifications fire at the scheduled time:
- Correct one: User’s chosen title/message, from the static reminder alarm (
scheduleId=daily_timesafari_reminder). - Extra one: Fallback message (“Daily Update” / “🌅 Good morning! Ready to make today amazing?”), from a second alarm that uses a UUID as
notification_id.
When the user edits an existing reminder (Edit Notification Details), only one notification fires. The duplicate only happens on initial setup.
The app calls scheduleDailyNotification once per user action in both flows (first-time and edit). The duplicate is caused inside the plugin by the prefetch worker scheduling a second alarm via the legacy DailyNotificationScheduler.
Evidence from Logcat
Filter: DNP-SCHEDULE, DailyNotificationWorker, DailyNotificationReceiver.
- 17:42:34 – Single call from app: plugin schedules the static reminder alarm (
scheduleId=daily_timesafari_reminder, source=INITIAL_SETUP). One OS alarm is scheduled. - 17:45:00 – Two
RECEIVE_STARTevents:- First:
display=5e373fd1-0f08-4e8f-b166-cfd46d694d82(UUID). - Second:
static_reminder id=daily_timesafari_reminder.
- First:
- Both run in parallel: Worker for UUID shows
DN|JIT_FRESH skip=trueand displays; Worker fordaily_timesafari_remindershowsDN|DISPLAY_STATIC_REMINDERand displays. So two notifications are shown.
Conclusion: two different PendingIntents fire at the same time: one with notification_id = UUID, one with notification_id = daily_timesafari_reminder.
Root cause (plugin side)
-
ScheduleHelper.scheduleDailyNotification (e.g. in
DailyNotificationPlugin.kt):- Cancels existing alarm for
scheduleId. - Schedules one alarm via NotifyReceiver.scheduleExactNotification with
reminderId = scheduleId,scheduleId = scheduleId,isStaticReminder = true(INITIAL_SETUP). That alarm carries title/body in the intent and is the “correct” notification. - Enqueues DailyNotificationFetchWorker (prefetch) to run 2 minutes before the same time.
- Cancels existing alarm for
-
DailyNotificationFetchWorker runs ~2 minutes before the display time:
- Tries to fetch content (e.g. native fetcher). For a static-reminder-only app (no URL, no fetcher returning content), the fetch returns empty/null.
- Goes to handleFailedFetch → useFallbackContent → getFallbackContent → createEmergencyFallbackContent(scheduledTime).
- createEmergencyFallbackContent builds a
NotificationContent()(default constructor), which assigns a random UUID asid, and sets title “Daily Update” and body “🌅 Good morning! Ready to make today amazing?”. - useFallbackContent then calls scheduleNotificationIfNeeded(fallbackContent).
-
scheduleNotificationIfNeeded uses the legacy DailyNotificationScheduler (AlarmManager) to schedule another alarm at the same
scheduledTime, withnotification_id= that UUID.
So at fire time there are two alarms:
- NotifyReceiver’s alarm:
notification_id=daily_timesafari_reminder,is_static_reminder= true → correct user message. - DailyNotificationScheduler’s alarm:
notification_id= UUID → fallback message.
The prefetch path is intended for “fetch content then display” flows. For static reminder schedules, the display is already fully handled by the single NotifyReceiver alarm; the prefetch worker should not schedule a second alarm.
Why edit doesn’t show the duplicate (in observed behavior)
On edit, the app still calls the plugin once and the plugin again enqueues the prefetch worker. Possible reasons the duplicate is less obvious on edit:
- Different timing (e.g. user sets a time further out, or doesn’t wait for the second notification).
- Or the first-time run leaves the prefetch/legacy path in a state where the duplicate only appears on first setup.
Regardless, the correct fix is to ensure that for static-reminder schedules the prefetch worker never schedules a second alarm.
Recommended fix (in the plugin)
Option A (recommended): Do not enqueue prefetch for static reminder schedules
In ScheduleHelper.scheduleDailyNotification (or equivalent), when scheduling a static reminder (title/body from app, no URL, display already in the intent), do not enqueue DailyNotificationFetchWorker for that run. The prefetch is for “fetch content then show”; for static reminders there is nothing to fetch and the only alarm should be the one from NotifyReceiver.
- No new inputData flags needed.
- No change to DailyNotificationFetchWorker semantics for other flows.
Option B: Prefetch worker skips scheduling when display is already scheduled
- When enqueueing the prefetch work for a static-reminder schedule, pass an input flag (e.g.
display_already_scheduledoris_static_reminder_schedule= true). - In DailyNotificationFetchWorker, in useFallbackContent (and anywhere else that calls scheduleNotificationIfNeeded for this work item), if that flag is set, do not call scheduleNotificationIfNeeded.
- Ensures only the NotifyReceiver alarm fires for that time.
Option A is simpler and matches the semantics: static reminder = one alarm, no prefetch.
App-side behavior (no change required)
- First-time reminder: Account view opens
PushNotificationPermissionwithoutskipSchedule. User sets time/message and confirms. Dialog’sturnOnNativeNotificationscallsNotificationService.scheduleDailyNotification(...)once and then the callback saves settings. No second schedule from the app. - Edit reminder: Account view opens the dialog with
skipSchedule: true. Only the parent’s callback runs; it callscancelDailyNotification()(on iOS) thenscheduleDailyNotification(...)once. No double schedule from the app.
So the duplicate is entirely due to the plugin’s prefetch worker scheduling an extra alarm via the legacy scheduler; fixing it in the plugin as above will resolve the issue.
Files to consider in the plugin
- ScheduleHelper.scheduleDailyNotification (e.g. in
DailyNotificationPlugin.kt): where the single NotifyReceiver alarm and the prefetch work are enqueued. Either skip enqueueing prefetch for static reminder (Option A), or add inputData for “display already scheduled” (Option B). - DailyNotificationFetchWorker:
useFallbackContent→scheduleNotificationIfNeeded; if using Option B, skipscheduleNotificationIfNeededwhen the new flag is set. - DailyNotificationScheduler (legacy): used by
scheduleNotificationIfNeededto add the second (UUID) alarm; no change required if the worker simply stops calling it for static-reminder schedules.
Verification
After the fix:
- First-time: Turn on Reminder Notification, set message and time (e.g. 2–3 minutes ahead). Wait until the scheduled time. Only one notification should appear, with the user’s message.
- Logcat should show a single
RECEIVE_STARTat that time (e.g.static_reminder id=daily_timesafari_reminder), and no seconddisplay=<uuid>for the same time.
You can reuse the same Logcat filter as above to confirm a single receiver run per scheduled time.