--- phase: 04-notifications plan: 01 subsystem: notifications tags: [flutter_local_notifications, timezone, flutter_timezone, shared_preferences, riverpod, android, drift] # Dependency graph requires: - phase: 03-daily-plan provides: DailyPlanDao with tasks/rooms database access - phase: 01-foundation provides: SharedPreferences pattern via ThemeNotifier provides: - NotificationService singleton wrapping FlutterLocalNotificationsPlugin - NotificationSettingsNotifier persisting enabled + TimeOfDay to SharedPreferences - DailyPlanDao one-shot queries for overdue and today task counts - Android build configured for flutter_local_notifications v21 - Timezone initialization in main.dart - 7 notification ARB strings for German locale affects: [04-02-settings-ui, future notification scheduling] # Tech tracking tech-stack: added: [flutter_local_notifications ^21.0.0, timezone ^0.11.0, flutter_timezone ^1.0.8] patterns: [singleton service for native plugin wrapper, @Riverpod(keepAlive) notifier with sync default + async load override] key-files: created: - lib/core/notifications/notification_service.dart - lib/core/notifications/notification_settings_notifier.dart - lib/core/notifications/notification_settings_notifier.g.dart - test/core/notifications/notification_service_test.dart - test/core/notifications/notification_settings_notifier_test.dart modified: - pubspec.yaml - android/app/build.gradle.kts - android/app/src/main/AndroidManifest.xml - lib/main.dart - lib/features/home/data/daily_plan_dao.dart - lib/features/home/data/daily_plan_dao.g.dart - lib/l10n/app_de.arb key-decisions: - "timezone constraint upgraded to ^0.11.0 — flutter_local_notifications v21 requires ^0.11.0, plan specified ^0.9.4" - "flutter_local_notifications v21 uses named parameters in initialize() and zonedSchedule() — updated from positional parameter style in RESEARCH.md examples" - "Generated Riverpod 3 provider named notificationSettingsProvider (not notificationSettingsNotifierProvider) — consistent with existing themeProvider naming convention" - "nextInstanceOf exposed as @visibleForTesting public method (not private _nextInstanceOf) to enable unit testing without mocking" - "Test helper makeContainer() awaits Future.delayed(Duration.zero) to let initial async _load() complete before mutating state assertions" patterns-established: - "Plain Dart singleton for native plugin wrapper: NotificationService uses factory constructor + static _instance, initialized once at app startup outside Riverpod" - "Sync default + async load pattern: @Riverpod(keepAlive: true) returns const default synchronously in build(), async _load() overrides state after SharedPreferences hydration" - "TDD with async state: test helper function awaits initial async load before running mutation tests to avoid race conditions" requirements-completed: [NOTF-01, NOTF-02] # Metrics duration: 9min completed: 2026-03-16 --- # Phase 4 Plan 1: Notification Infrastructure Summary **flutter_local_notifications v21 singleton service with TZ-aware scheduling, Riverpod keepAlive settings notifier persisting to SharedPreferences, Android desugaring config, and DailyPlanDao one-shot task count queries** ## Performance - **Duration:** 9 min - **Started:** 2026-03-16T13:48:28Z - **Completed:** 2026-03-16T13:57:42Z - **Tasks:** 2 - **Files modified:** 11 ## Accomplishments - Android build fully configured for flutter_local_notifications v21: compileSdk=35, core library desugaring enabled, permissions and receivers in AndroidManifest - NotificationService singleton wrapping FlutterLocalNotificationsPlugin with initialize, requestPermission, scheduleDailyNotification, cancelAll, and @visibleForTesting nextInstanceOf - NotificationSettingsNotifier with @Riverpod(keepAlive: true) persisting enabled/time to SharedPreferences, following ThemeNotifier pattern - DailyPlanDao extended with getOverdueAndTodayTaskCount and getOverdueTaskCount one-shot Future queries - Timezone initialization chain in main.dart: initializeTimeZones → getLocalTimezone → setLocalLocation → NotificationService.initialize - 7 German ARB strings for notification UI and content - 12 new unit tests (5 service, 7 notifier) plus all 72 existing tests passing (84 total) ## Task Commits Each task was committed atomically: 1. **Task 1: Android config, packages, NotificationService, timezone init, DAO query, ARB strings** - `8787671` (feat) 2. **Task 2 RED: Failing tests for NotificationSettingsNotifier and NotificationService** - `0f6789b` (test) 3. **Task 2 GREEN: NotificationSettingsNotifier implementation + fixed tests** - `4f72eac` (feat) **Plan metadata:** (docs commit — see final commit hash below) _Note: TDD task 2 has separate test (RED) and implementation (GREEN) commits per TDD protocol_ ## Files Created/Modified - `lib/core/notifications/notification_service.dart` - Singleton wrapping FlutterLocalNotificationsPlugin; scheduleDailyNotification uses zonedSchedule with TZDateTime - `lib/core/notifications/notification_settings_notifier.dart` - @Riverpod(keepAlive: true) notifier; NotificationSettings data class with enabled + time - `lib/core/notifications/notification_settings_notifier.g.dart` - Riverpod code gen; provider named notificationSettingsProvider - `test/core/notifications/notification_service_test.dart` - Unit tests for singleton pattern and nextInstanceOf TZ logic - `test/core/notifications/notification_settings_notifier_test.dart` - Unit tests for default state, setEnabled, setTime, and persistence loading - `pubspec.yaml` - Added flutter_local_notifications ^21.0.0, timezone ^0.11.0, flutter_timezone ^1.0.8 - `android/app/build.gradle.kts` - compileSdk=35, isCoreLibraryDesugaringEnabled=true, desugar_jdk_libs:2.1.4 dependency - `android/app/src/main/AndroidManifest.xml` - POST_NOTIFICATIONS + RECEIVE_BOOT_COMPLETED permissions, ScheduledNotificationReceiver + ScheduledNotificationBootReceiver - `lib/main.dart` - async main with timezone init chain and NotificationService.initialize() - `lib/features/home/data/daily_plan_dao.dart` - Added getOverdueAndTodayTaskCount and getOverdueTaskCount one-shot queries - `lib/l10n/app_de.arb` - 7 new keys: settingsSectionNotifications, notificationsEnabledLabel, notificationsTimeLabel, notificationsPermissionDeniedHint, notificationTitle, notificationBody, notificationBodyWithOverdue ## Decisions Made - **timezone version upgraded to ^0.11.0**: Plan specified ^0.9.4, but flutter_local_notifications v21 requires ^0.11.0. Auto-fixed (Rule 3 — blocking). - **v21 named parameter API**: RESEARCH.md examples used old positional parameter style. v21 uses `settings:`, `id:`, `scheduledDate:`, `notificationDetails:` named params. Fixed to match actual API. - **Riverpod 3 naming convention**: Generated provider is `notificationSettingsProvider` not `notificationSettingsNotifierProvider`, consistent with existing `themeProvider` decision from Phase 1. - **nextInstanceOf public @visibleForTesting**: Made public with annotation instead of private `_nextInstanceOf` to enable unit testing without native dispatch mocking. - **makeContainer() async helper**: Test helper awaits `Future.delayed(Duration.zero)` after first read to let the async `_load()` from `build()` complete before mutation tests run, preventing race conditions. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] timezone package version constraint incompatible** - **Found during:** Task 1 (flutter pub get) - **Issue:** Plan specified `timezone: ^0.9.4` but flutter_local_notifications v21 depends on `timezone: ^0.11.0` — pub solve failed immediately - **Fix:** Updated constraint to `^0.11.0` in pubspec.yaml - **Files modified:** pubspec.yaml - **Verification:** `flutter pub get` resolved successfully - **Committed in:** 8787671 **2. [Rule 1 - Bug] flutter_local_notifications v21 uses named parameters** - **Found during:** Task 2 (first test run against NotificationService) - **Issue:** RESEARCH.md pattern and plan used positional parameters for `_plugin.initialize()` and `_plugin.zonedSchedule()`. flutter_local_notifications v21 changed to named parameters — compile error "Too many positional arguments" - **Fix:** Updated NotificationService to use `settings:`, `id:`, `scheduledDate:`, `notificationDetails:`, `androidScheduleMode:` named params - **Files modified:** lib/core/notifications/notification_service.dart - **Verification:** `dart analyze` clean, tests pass - **Committed in:** 4f72eac **3. [Rule 1 - Bug] Riverpod 3 generated provider name is notificationSettingsProvider** - **Found during:** Task 2 (test compilation) - **Issue:** Tests referenced `notificationSettingsNotifierProvider` but Riverpod 3 code gen for `NotificationSettingsNotifier` produces `notificationSettingsProvider` — consistent with existing pattern - **Fix:** Updated all test references to use `notificationSettingsProvider` - **Files modified:** test/core/notifications/notification_settings_notifier_test.dart - **Verification:** Tests compile and pass - **Committed in:** 4f72eac **4. [Rule 1 - Bug] Async _load() race condition in tests** - **Found during:** Task 2 (setTime test failure) - **Issue:** `setTime(9:30)` persisted correctly but state read back as `(9:00)` because the async `_load()` from `build()` ran after `setTime`, resetting state to SharedPreferences defaults (hour=7, minute=0 since prefs were empty) - **Fix:** Added `makeContainer()` async helper that awaits `Future.delayed(Duration.zero)` to let initial `_load()` complete before mutations - **Files modified:** test/core/notifications/notification_settings_notifier_test.dart - **Verification:** All 7 notifier tests pass consistently - **Committed in:** 4f72eac --- **Total deviations:** 4 auto-fixed (1 blocking dependency, 3 bugs from API mismatch/race condition) **Impact on plan:** All auto-fixes were necessary for correctness. No scope creep. ## Issues Encountered - flutter_local_notifications v21 breaking changes (named params, compileSdk requirement) were not fully reflected in RESEARCH.md examples — all caught and fixed during compilation/test runs. ## User Setup Required None — no external service configuration required. ## Next Phase Readiness - NotificationService and NotificationSettingsNotifier fully implemented and tested - Plan 02 can immediately wire notificationSettingsProvider into SettingsScreen - notificationSettingsProvider (notificationSettings.dart) exports are ready for import - ScheduledNotificationBootReceiver is registered and exported=true for Android 12+ - Timezone is initialized at app start — no further setup needed for Plan 02 --- *Phase: 04-notifications* *Completed: 2026-03-16* ## Self-Check: PASSED - lib/core/notifications/notification_service.dart: FOUND - lib/core/notifications/notification_settings_notifier.dart: FOUND - lib/core/notifications/notification_settings_notifier.g.dart: FOUND - test/core/notifications/notification_service_test.dart: FOUND - test/core/notifications/notification_settings_notifier_test.dart: FOUND - .planning/phases/04-notifications/04-01-SUMMARY.md: FOUND - commit 8787671: FOUND - commit 0f6789b: FOUND - commit 4f72eac: FOUND