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