# iOS Rollover Implementation — Open Questions & Answers **Date**: 2025-01-27 **Status**: Pre-Implementation Decisions --- ## Question 1: Fetcher Integration **Question**: How should we integrate DailyNotificationFetcher for prefetch scheduling? (Phase 2) ### Current State Analysis - **Android**: Uses `DailyNotificationFetcher.scheduleFetch(fetchTime)` and `scheduleImmediateFetch()` - **iOS**: Has `DailyNotificationBackgroundTaskManager` with `scheduleBackgroundTask()` method - **iOS Pattern**: Uses `BGTaskScheduler` with `BGAppRefreshTaskRequest` ### Recommendation: **Defer to Phase 2, Use Placeholder Pattern** **Rationale**: 1. **Phase 1 Focus**: Core rollover functionality (scheduling next notification) 2. **Prefetch is Separate**: Prefetch scheduling is independent of rollover 3. **Existing Infrastructure**: iOS already has background task infrastructure 4. **Android Pattern**: Android also separates rollover from prefetch (optional parameter) ### Implementation Approach **Phase 1 (Current)**: - Make `fetcher` parameter optional in `scheduleNextNotification()` - Add TODO comments for Phase 2 integration - Log prefetch scheduling intent (even if not executed) **Phase 2 (Future)**: - Create `DailyNotificationFetcher` class (iOS equivalent) - Integrate with `DailyNotificationBackgroundTaskManager` - Use `BGTaskScheduler` for prefetch scheduling - Calculate fetch time: `nextScheduledTime - (5 * 60 * 1000)` (5 minutes before) ### Code Pattern ```swift // Phase 1: Optional fetcher, log intent if let fetcher = fetcher { let fetchTime = nextScheduledTime - (5 * 60 * 1000) // TODO: Phase 2 - Implement fetcher.scheduleFetch(fetchTime) print("\(Self.TAG): RESCHEDULE_PREFETCH_SCHEDULED id=\(content.id) next_fetch=\(fetchTime)") } else { print("\(Self.TAG): RESCHEDULE_PREFETCH_SKIP id=\(content.id) fetcher_not_available") } ``` **Decision**: ✅ **Defer to Phase 2, use optional parameter pattern** --- ## Question 2: AppDelegate Access **Question**: Is there a better way to access the plugin from AppDelegate without using Capacitor bridge? ### Current State Analysis - **Capacitor Pattern**: Uses `CAPBridgeViewController` to access plugins - **Test App**: Already uses this pattern for other operations - **Production Apps**: May have different AppDelegate structures ### Recommendation: **Use Notification Center Pattern** **Rationale**: 1. **Decoupling**: AppDelegate doesn't need direct plugin reference 2. **Flexibility**: Works across different app architectures 3. **Reliability**: Notification center is always available 4. **Testability**: Easier to test without Capacitor dependency ### Implementation Approach **Option A: Notification Center (Recommended)** - Plugin registers for notification delivery events - AppDelegate posts notification when delivery detected - Plugin handles rollover in response to notification **Option B: Capacitor Bridge (Fallback)** - Use existing bridge pattern - Works but creates tight coupling - Use as fallback if notification center doesn't work ### Code Pattern ```swift // In DailyNotificationPlugin.load(): NotificationCenter.default.addObserver( self, selector: #selector(handleNotificationDelivery(_:)), name: NSNotification.Name("DailyNotificationDelivered"), object: nil ) // In AppDelegate.willPresent: NotificationCenter.default.post( name: NSNotification.Name("DailyNotificationDelivered"), object: nil, userInfo: [ "notification_id": notificationId, "scheduled_time": scheduledTime ] ) ``` **Decision**: ✅ **Use Notification Center pattern, with Capacitor bridge as fallback** --- ## Question 3: Background Task **Question**: Should we add a dedicated background task for rollover detection, or rely on existing recovery mechanisms? ### Current State Analysis - **Existing Recovery**: `DailyNotificationReactivationManager` already runs on app launch - **Background Tasks**: iOS has strict limits on background execution - **Reliability**: Multiple detection mechanisms increase reliability ### Recommendation: **Rely on Existing Recovery + AppDelegate** **Rationale**: 1. **iOS Limitations**: Background tasks are unreliable (system-controlled) 2. **Existing Infrastructure**: Recovery manager already handles app launch scenarios 3. **Coverage**: AppDelegate (foreground) + Recovery (background) covers all cases 4. **Simplicity**: Fewer moving parts = fewer failure points ### Implementation Approach **Two Detection Mechanisms**: 1. **Foreground**: AppDelegate `willPresent` → immediate rollover 2. **Background**: Recovery Manager → rollover on app launch **No Dedicated Background Task**: - Background tasks are unreliable (system decides when to run) - Recovery manager already covers app launch scenarios - Adding another mechanism adds complexity without significant benefit ### Code Pattern ```swift // Detection Mechanism 1: Foreground (AppDelegate) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, ...) { // Trigger rollover immediately await handleNotificationRollover(...) } // Detection Mechanism 2: Background (Recovery Manager) func performColdStartRecovery() async { // Check for delivered notifications await checkAndProcessDeliveredNotifications() } ``` **Decision**: ✅ **Rely on existing recovery + AppDelegate, no dedicated background task** --- ## Question 4: Error Handling **Question**: How should we handle rollover failures? Retry? Log? User notification? ### Current State Analysis - **Android Pattern**: Logs errors, continues execution (non-fatal) - **iOS Recovery Manager**: Catches all errors, logs, continues (non-fatal) - **User Experience**: Failures should be silent (background operation) ### Recommendation: **Log + Continue (Non-Fatal)** **Rationale**: 1. **Background Operation**: Rollover is background, shouldn't interrupt user 2. **Recovery Available**: Recovery manager will catch missed rollovers on next app launch 3. **Consistency**: Matches Android and existing iOS recovery patterns 4. **User Experience**: Silent failures, recovery on next launch ### Implementation Approach **Error Handling Strategy**: 1. **Log Errors**: Comprehensive logging for debugging 2. **Continue Execution**: Don't crash or interrupt app 3. **No Retry**: Let recovery manager handle on next launch 4. **No User Notification**: Background operation, silent failure 5. **History Recording**: Record failures in history (if history implemented) ### Code Pattern ```swift func scheduleNextNotification(...) async -> Bool { do { // Rollover logic return true } catch { print("\(Self.TAG): RESCHEDULE_ERR id=\(content.id) err=\(error.localizedDescription)") // Log error but don't throw - let recovery handle on next launch return false } } // In recovery manager: if !scheduled { print("\(Self.TAG): Failed to roll over delivered notification id=\(notificationId)") // Recovery will retry on next app launch } ``` **Decision**: ✅ **Log + Continue (non-fatal), no retry, no user notification** --- ## Question 5: Performance **Question**: Should we batch rollover operations or process individually? ### Current State Analysis - **Android Pattern**: Processes individually (one notification at a time) - **iOS Recovery**: Processes notifications individually - **Volume**: Typically 1-2 notifications per day (low volume) ### Recommendation: **Process Individually** **Rationale**: 1. **Low Volume**: Typically 1 notification per day, batching unnecessary 2. **Simplicity**: Individual processing is simpler and easier to debug 3. **Error Isolation**: Individual processing isolates failures 4. **Consistency**: Matches Android and existing iOS patterns ### Implementation Approach **Individual Processing**: - Process each notification rollover separately - Each rollover is independent operation - Failures in one don't affect others - Easier to log and debug **Future Optimization** (if needed): - If volume increases, consider batching - Current volume doesn't justify batching complexity ### Code Pattern ```swift // Process individually (current approach) for notification in deliveredNotifications { await scheduler.scheduleNextNotification(notification, ...) } // Batching would look like: // await scheduler.scheduleNextNotificationsBatch(notifications, ...) // But not needed for current volume ``` **Decision**: ✅ **Process individually (current volume doesn't justify batching)** --- ## Question 6: Testing **Question**: Do we need automated tests for rollover, or is manual testing sufficient for Phase 1? ### Current State Analysis - **Existing Tests**: iOS has unit tests for recovery manager - **Test Coverage**: Some components have tests, others don't - **Phase 1 Scope**: Core rollover functionality ### Recommendation: **Manual Testing for Phase 1, Automated Tests for Phase 2** **Rationale**: 1. **Phase 1 Focus**: Core functionality, manual testing sufficient 2. **Complexity**: Rollover involves system notifications (hard to test automatically) 3. **Time Investment**: Automated tests take time, manual testing faster for Phase 1 4. **Phase 2**: Add automated tests when edge cases are implemented ### Implementation Approach **Phase 1 Testing**: - Manual testing checklist - Test scenarios: foreground delivery, background delivery, duplicates - Real device testing (simulator may not handle notifications correctly) **Phase 2 Testing**: - Unit tests for time calculations (DST, timezone) - Integration tests for rollover flow - Edge case tests (time changes, timezone changes) ### Test Checklist (Phase 1) 1. ✅ **Foreground Delivery**: App running, notification fires → rollover triggers 2. ✅ **Background Delivery**: App not running, notification fires → rollover on launch 3. ✅ **Duplicate Prevention**: Multiple rollover attempts → only one scheduled 4. ✅ **DST Transition**: Schedule on DST day → correct time calculation 5. ✅ **Error Handling**: Simulate failure → graceful degradation **Decision**: ✅ **Manual testing for Phase 1, automated tests for Phase 2** --- ## Summary of Decisions | Question | Decision | Rationale | |----------|----------|-----------| | **Fetcher Integration** | Defer to Phase 2, optional parameter | Prefetch is separate concern, Phase 1 focuses on core rollover | | **AppDelegate Access** | Notification Center pattern | Decoupling, flexibility, reliability | | **Background Task** | Rely on existing recovery | iOS limitations, existing infrastructure sufficient | | **Error Handling** | Log + Continue (non-fatal) | Background operation, recovery handles failures | | **Performance** | Process individually | Low volume, simplicity, consistency | | **Testing** | Manual for Phase 1, automated for Phase 2 | Phase 1 scope, complexity, time investment | --- ## Implementation Impact ### Changes to Review Document Based on these decisions, the review document should be updated: 1. **Fetcher Parameter**: Make optional, add Phase 2 TODOs 2. **AppDelegate Pattern**: Use Notification Center instead of Capacitor bridge 3. **Background Task**: Remove dedicated background task, rely on recovery 4. **Error Handling**: Add comprehensive logging, non-fatal errors 5. **Performance**: Individual processing (no batching) 6. **Testing**: Manual testing checklist for Phase 1 ### Next Steps 1. ✅ **Decisions Made** (This document) 2. **Update Review Document** with decisions 3. **Update Implementation Plan** with specific patterns 4. **Begin Phase 1 Implementation** --- ## References - Review Document: `docs/ios-rollover-implementation-review.md` - Edge Case Plan: `docs/ios-rollover-edge-case-plan.md` - Android Implementation: `android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java` - iOS Recovery Manager: `ios/Plugin/DailyNotificationReactivationManager.swift` - iOS Background Tasks: `ios/Plugin/DailyNotificationBackgroundTaskManager.swift`