# 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:

1. **Correct one:** User’s chosen title/message, from the static reminder alarm (`scheduleId` = `daily_timesafari_reminder`).
2. **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_START` events:
  - First: `display=5e373fd1-0f08-4e8f-b166-cfd46d694d82` (UUID).
  - Second: `static_reminder id=daily_timesafari_reminder`.
- Both run in parallel: Worker for UUID shows `DN|JIT_FRESH skip=true` and displays; Worker for `daily_timesafari_reminder` shows `DN|DISPLAY_STATIC_REMINDER` and 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)

1. **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.

2. **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** as `id`, and sets title “Daily Update” and body “🌅 Good morning! Ready to make today amazing?”.
   - **useFallbackContent** then calls **scheduleNotificationIfNeeded(fallbackContent)**.

3. **scheduleNotificationIfNeeded** uses the **legacy DailyNotificationScheduler** (AlarmManager) to schedule **another** alarm at the **same** `scheduledTime`, with `notification_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_scheduled` or `is_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 `PushNotificationPermission` without `skipSchedule`. User sets time/message and confirms. Dialog’s `turnOnNativeNotifications` calls `NotificationService.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 calls `cancelDailyNotification()` (on iOS) then `scheduleDailyNotification(...)` **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, skip `scheduleNotificationIfNeeded` when the new flag is set.
- **DailyNotificationScheduler** (legacy): used by `scheduleNotificationIfNeeded` to add the second (UUID) alarm; no change required if the worker simply stops calling it for static-reminder schedules.

---

## Verification

After the fix:

1. **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.
2. Logcat should show a single `RECEIVE_START` at that time (e.g. `static_reminder id=daily_timesafari_reminder`), and no second `display=<uuid>` for the same time.

You can reuse the same Logcat filter as above to confirm a single receiver run per scheduled time.
