fix(notifications): make both automatic reminders actually fire - #516
Open
MOHITKOURAV01 wants to merge 1 commit into
Open
fix(notifications): make both automatic reminders actually fire#516MOHITKOURAV01 wants to merge 1 commit into
MOHITKOURAV01 wants to merge 1 commit into
Conversation
Both reminders were on by default, rescheduled from `main.dart` on every
launch, and neither reliably fired. Every failure was a bare `return`, so
the toggle in Settings stayed switched on while nothing was registered
with the OS.
**The period reminder gave up as soon as `last_period` went stale.** It
projected exactly one cycle from the stored anchor:
final predictedDate = lastPeriod.add(Duration(days: cycleLength));
final reminderDate = predictedDate.subtract(Duration(days: daysBefore));
if (reminderDate.isBefore(now)) return;
Once `now` passed that date it was permanently in the past, on that
launch and every launch after. The anchor only moves when a period is
logged — so the reminder worked for the woman already logging diligently
and switched itself off for the woman who had stopped, who is the one the
nudge exists to reach.
It now projects forward cycle by cycle until the reminder date is ahead,
with a 90-day cap measured from the anchor. Past that cap the anchor is
not evidence about this month, so instead of a confident date built on
nothing (the failure ishita2740#487 describes on the calendar) it schedules "log
your last period" — honest and actionable, where scheduling nothing was
neither.
**The "daily" logging reminder was a one-shot.** No
`matchDateTimeComponents`, so the OS never repeated it, and an early
return for anyone opening the app after 19:00 — which is when people
check a tracker. It is now genuinely daily and rolls to tomorrow when
today's time has gone. The "already logged today" check no longer gates
scheduling: it was evaluated once at launch, so logging at 09:00 still
produced a 19:00 nudge, and logging before launch produced no reminder
for any later day either.
**Nothing was ever delivered on iOS.** Every `NotificationDetails` was
`android:`-only, and `requestPermissions()` only asked
`permission_handler`, which does not obtain the plugin's alert
permission there. All four now carry `DarwinNotificationDetails`, and
the iOS request goes through the plugin's own resolver.
**`scheduleAllAutomaticNotifications` only ever added.** With one toggle
on and one off, the early return was skipped and the disabled reminder's
already-registered notification was left in place — so a reminder
switched off in Settings kept arriving. It now reconciles both ways.
The user-visible channel called "Test Alerts" is renamed; Android channel
names show in system notification settings.
The arithmetic moves to `services/reminder_schedule.dart` as pure
functions of `(anchor, now)`, so it can be tested without the plugin, a
device or the wall clock — none of it was testable before. Notification
text is now localized through `lookupAppLocalizations` rather than
hard-coded English, since the strings are composed outside the widget
tree where there is no BuildContext.
Fixes ishita2740#511
|
@MOHITKOURAV01 is attempting to deploy a commit to the ishita2740's projects Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #511.
The bug
Two automatic reminders, both on by default (
periodPredictionRemindersandloggingRemindersaredefaultValue: true), both rescheduled frommain.darton every launch. Neither one reliably fired.Every failure was a bare
return. Nothing told the user, and the Settings toggle stayed switched on while nothing was registered with the OS.1. The period reminder gave up as soon as
last_periodwent staleOne anchor, one cycle. Once
nowpassed that date,reminderDatewas permanently in the past and this returned without scheduling — on that launch and every launch after it.The anchor only moves when the user logs a period. So the failure mode is exactly inverted from what a reminder is for: it worked for the woman already logging diligently, and switched itself off for the woman who had stopped — who is the one the nudge exists to reach. Log a period on 1 August, don't open the app, and from 31 August onward you are never reminded again.
2. The "daily" logging reminder fires at most once, usually zero times
The doc comment said "Schedule a daily reminder". The body scheduled a one-shot:
zonedSchedulerepeats only withmatchDateTimeComponents: DateTimeComponents.time. Without it this is one notification at one instant.scheduledDateis today at 19:00 and that is already past. Evening is when people check a tracker.3. Nothing was ever delivered on iOS
Every
NotificationDetailsin the file wasNotificationDetails(android: ...)with noiOS:entry, so the plugin had nothing to present. Compounding it,init()passesrequestAlertPermission: false/requestBadgePermission: false/requestSoundPermission: false, andrequestPermissions()only askedPermission.notificationviapermission_handler— which does not obtain the plugin's alert permission on iOS. So the permission was never granted and the details were never provided.4.
scheduleAllAutomaticNotificationsonly ever addedWith one toggle on and one off, the early return is skipped and the disabled reminder's already-registered notification is left in place. Settings cancels on the off-switch, but this launch-time path never reconciled — so a reminder turned off kept being delivered.
5. A channel called "Test Alerts"
AndroidNotificationDetails('test_channel', 'Test Alerts', ...). Android channel names are user-visible in system notification settings.The change
lib/services/reminder_schedule.dart— the arithmetic, as pure functionsPure functions of
(anchor, now). None of this was testable before; it was all inline in a service that talks to the plugin.planPeriodReminderwalks forward one cycle at a time from the anchor until the reminder date is ahead ofnow, so a stale anchor produces a reminder rather than silence. It stops at 90 days past the anchor — roughly three cycles — and returnsPeriodReminderKind.logPeriod:That cap is the point of the three-valued return. Projecting a fourth cycle from an anchor that old produces a confident-looking prediction built on nothing, which is the failure #487 describes on the calendar. Saying "we've lost track" is honest and actionable; scheduling nothing was neither.
The nudge is scheduled for tomorrow morning, not this instant — the app is being launched right now, so a notification this second would fire over the screen the user is already looking at.
Two more cases that used to be silence:
cycle_lengthis clamped to the population default, using the same 15–60 day band as the backend'sprediction_service.MIN_PLAUSIBLE_CYCLE_DAYS/MAX_PLAUSIBLE_CYCLE_DAYS. A profile carrying 400 must not produce a reminder in the next century.nextDailyOccurrencereturns today's time if it is still ahead and tomorrow's otherwise. There is no case in which "that time has gone, so schedule nothing" is what a user asked for by leaving the toggle on.NotificationServiceschedulePeriodPredictionReminderreturns thePeriodReminderKindso a caller — and a test — can tell the three outcomes apart, instead of watching a method returnvoidwhether it did anything or not.matchDateTimeComponents: DateTimeComponents.timeon the logging reminder._details()helper, and every notification now carriesDarwinNotificationDetails.requestPermissions()goes throughresolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()on iOS and falls back topermission_handlerelsewhere.scheduleAllAutomaticNotificationscancels a disabled reminder instead of skipping it.test_channel/ "Test Alerts" →rhythma_reminders_channel/ a localized name.Localization
The notification text was hard-coded English. It is composed outside the widget tree, so there is no
BuildContextforAppLocalizations.of()— but the generatedlookupAppLocalizations(Locale)takes a locale directly, and the chosen language is already in settings. Seven keys added across the ARBs and the committed generated Dart, translated in the eight locales that carry translations.The lookup is wrapped: an unsupported code stored by an older build, or a settings box that is not open on some path, falls back to English rather than throwing — a crash on a background scheduling pass would take the reminder down with it, which is the class of bug this PR is about.
Tests
test/services/reminder_schedule_test.dart— 22 tests. The ones that state the issue:rolls forward when the first projected cycle has already passed— a June anchor, projected across three cycles to September.an anchor older than the projection horizon asks her to log— a year-old anchor,shouldScheduleis still true.rolls to tomorrow when today's time has gone— the 21:00 launch that used to get nothing.Plus the boundaries: a reminder date that passed earlier today, a zero and a negative lead time, the configurable horizon, an anchor in the future, month-boundary rollover, and the cycle-length clamping in both directions.
Notes for review
I could not run these. No Flutter or Dart toolchain on this machine, so
flutter testandflutter analyzehave not been run against this branch. Same caveat as #515.flutter analyzeis red onmainregardless (#492, with #493 open against it).The riskiest line is the iOS permission request:
I believe that class and
requestPermissions({alert, badge, sound})are correct forflutter_local_notifications: ^22.0.1, but it is the one API here I could not verify by compiling. Worth a look from anyone with the SDK to hand.Other things to push back on:
uiLocalNotificationDateInterpretationis deliberately not added, despite [Flutter] Missing mandatory 'uiLocalNotificationDateInterpretation' in NotificationService causes app crash #278. That parameter was removed fromflutter_local_notificationsin v19; the pubspec pins^22.0.1, so adding it would not compile. [Flutter] Missing mandatory 'uiLocalNotificationDateInterpretation' in NotificationService causes app crash #278 looks stale rather than wrong-at-the-time.zonedSchedule. Doing it properly needs a background callback; that felt like a bigger change than this issue asked for, and the current behaviour (a mistimed nudge) is better than the old one (no nudge at all, ever).