Files
crowd-funder-for-time-pwa/doc/plugin-android-edit-reschedule-alarm-not-firing.md
Jose Olarte III 0e096b1a46 Fix: Android daily notification: single schedule on edit, no double-cancel
Resolves long-standing issue where the second scheduled time (after editing
the reminder) did not fire on Android.

- PushNotificationPermission: add open(..., options?: { skipSchedule }).
  When skipSchedule is true (edit flow), dialog only invokes callback with
  time/message; parent is sole scheduler so the plugin is not called twice.
- AccountViewView: pass { skipSchedule: true } when opening the dialog for
  edit; keep cancel (iOS only) + single scheduleDailyNotification in callback.
- NativeNotificationService: serialize scheduleDailyNotification so only one
  schedule runs at a time (scheduleLock + doScheduleDailyNotification).
- AccountViewView: guard edit-reminder callback with editReminderScheduleInProgress
  so one schedule per user action.
- Gate pre-cancel on Android in edit flow (CONSUMING_APP brief): skip
  cancelDailyNotification before schedule on Android; plugin cancels internally.
- Use single stable reminder id and always pass id on both platforms (plugin 1.1.2+).
- Add doc/plugin-android-edit-reschedule-alarm-not-firing.md for plugin repo
  (cancel-before-reschedule may cancel the PendingIntent used for setAlarmClock).
2026-02-16 21:25:07 +08:00

2.6 KiB
Raw Blame History

Plugin: Android — Alarm set after edit doesnt fire (cancel-before-reschedule)

Context: Consuming app (TimeSafari) — user sets reminder at 6:57pm (fires), then edits to 7:00pm. Only one scheduleDailyNotification call is made (skipSchedule fix). Logs show "Scheduling OS alarm" and "Updated schedule in database" for 19:00, but the notification never fires at 7:00pm.

Likely cause (plugin): In NotifyReceiver.kt, before calling setAlarmClock(pendingIntent) the code:

  1. Creates pendingIntent with PendingIntent.getBroadcast(..., requestCode, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE).
  2. Gets existingPendingIntent with PendingIntent.getBroadcast(..., requestCode, intent, FLAG_NO_CREATE | FLAG_IMMUTABLE) (same requestCode, same intent).
  3. If not null: alarmManager.cancel(existingPendingIntent) and existingPendingIntent.cancel().
  4. Then calls alarmManager.setAlarmClock(alarmClockInfo, pendingIntent).

On Android, PendingIntent equality for caching is based on requestCode and Intent (action, component, etc.), not necessarily all extras. So existingPendingIntent is often the same (cached) PendingIntent as pendingIntent. Then we call existingPendingIntent.cancel(), which cancels that PendingIntent for future use. We then use the same (now cancelled) PendingIntent in setAlarmClock(..., pendingIntent). On some devices/versions, setting an alarm with a cancelled PendingIntent can result in the alarm not firing.

Suggested fix (plugin repo):

  • Remove the existingPendingIntent.cancel() call. Use only alarmManager.cancel(existingPendingIntent) to clear any existing alarm for this requestCode. That way the PendingIntent we pass to setAlarmClock is not cancelled; only the previous alarm is removed.
  • Optionally: only run the “cancel existing” block when we know there was a previous schedule (e.g. from DB) for this scheduleId that hasnt fired yet, so we dont cancel when the previous alarm already fired (e.g. user edited after first fire).

Verification:

  • In the consuming app: set reminder 23 min from now, let it fire, then edit to 23 min from then and save. Capture logcat through the second scheduled time.
  • If the receiver never logs at the second time, the OS didnt deliver the alarm; fixing the cancel-before-reschedule logic as above should be tried first in the plugin.

References:

  • CONSUMING_APP_ANDROID_NOTES.md (double schedule, alarm scheduled but not firing).
  • NotifyReceiver.kt around “Cancelling existing alarm before rescheduling” and the following setAlarmClock use of pendingIntent.