396 lines
14 KiB
Markdown
396 lines
14 KiB
Markdown
# iOS Implementation Directive: App Launch Recovery & Missed Notification Detection
|
||
|
||
**Author**: Matthew Raymer
|
||
**Date**: 2025-12-08
|
||
**Status**: Active Implementation Directive - iOS Only
|
||
**Version**: 1.0.0
|
||
**Last Synced With Plugin Version**: v1.1.0
|
||
|
||
## Purpose
|
||
|
||
This directive provides **descriptive overview and integration guidance** for iOS-specific recovery and missed notification detection:
|
||
|
||
1. App Launch Recovery (cold/warm/terminated)
|
||
2. Missed Notification Detection
|
||
3. App Termination Detection
|
||
4. Background Task Registration for Boot Recovery
|
||
|
||
**⚠️ CRITICAL**: This document is **descriptive and integrative**. The **normative implementation instructions** are in the Phase 1–3 directives below. **If any code or behavior in this file conflicts with a Phase directive, the Phase directive wins.**
|
||
|
||
**Reference**: See [Plugin Requirements](./alarms/03-plugin-requirements.md) for requirements that Phase directives implement.
|
||
|
||
**Reference**: See [Platform Capability Reference](./alarms/01-platform-capability-reference.md) for iOS OS-level facts.
|
||
|
||
**⚠️ IMPORTANT**: For implementation, use the phase-specific directives (these are the canonical source of truth):
|
||
|
||
- **[Phase 1: Cold Start Recovery](./ios-implementation-directive-phase1.md)** - Minimal viable recovery
|
||
- Implements: [Plugin Requirements §3.1.2](./alarms/03-plugin-requirements.md#312-app-cold-start) (iOS equivalent)
|
||
- Explicit acceptance criteria, rollback safety, data integrity checks
|
||
- **Start here** for fastest implementation
|
||
|
||
- **[Phase 2: App Termination Detection & Recovery](./ios-implementation-directive-phase2.md)** - Comprehensive termination handling
|
||
- Implements: iOS-specific app termination scenarios
|
||
- Prerequisite: Phase 1 complete
|
||
|
||
- **[Phase 3: Background Task Registration & Boot Recovery](./ios-implementation-directive-phase3.md)** - Background task enhancement
|
||
- Implements: BGTaskScheduler registration for boot recovery
|
||
- Prerequisites: Phase 1 and Phase 2 complete
|
||
|
||
**See Also**: [Unified Alarm Directive](./alarms/000-UNIFIED-ALARM-DIRECTIVE.md) for master coordination document.
|
||
|
||
---
|
||
|
||
## 1. Implementation Overview
|
||
|
||
### 1.1 What Needs to Be Implemented
|
||
|
||
| Feature | Status | Priority | Location |
|
||
| ------- | ------ | -------- | -------- |
|
||
| App Launch Recovery | ❌ Missing | **High** | `DailyNotificationPlugin.swift` - `load()` method |
|
||
| Missed Notification Detection | ⚠️ Partial | **High** | `DailyNotificationPlugin.swift` - new method |
|
||
| App Termination Detection | ❌ Missing | **High** | `DailyNotificationPlugin.swift` - recovery logic |
|
||
| Background Task Registration | ⚠️ Partial | **Medium** | `AppDelegate.swift` - BGTaskScheduler registration |
|
||
|
||
### 1.2 Implementation Strategy
|
||
|
||
**Phase 1** – Cold start recovery only
|
||
- Missed notification detection + future notification verification
|
||
- No termination detection, no boot handling
|
||
- **See [Phase 1 directive](./ios-implementation-directive-phase1.md) for implementation**
|
||
|
||
**Phase 2** – App termination detection & full recovery
|
||
- Termination detection via UNUserNotificationCenter state comparison
|
||
- Comprehensive recovery of all schedules (notify + fetch)
|
||
- Past notifications marked as missed, future notifications rescheduled
|
||
- **See [Phase 2 directive](./ios-implementation-directive-phase2.md) for implementation**
|
||
|
||
**Phase 3** – Background task registration & boot recovery
|
||
- BGTaskScheduler registration for boot recovery
|
||
- Next occurrence rescheduled for repeating schedules
|
||
- **See [Phase 3 directive](./ios-implementation-directive-phase3.md) for implementation**
|
||
|
||
---
|
||
|
||
## 2. iOS-Specific Considerations
|
||
|
||
### 2.1 Key Differences from Android
|
||
|
||
**iOS Advantages**:
|
||
- ✅ Notifications persist across app termination (OS-guaranteed)
|
||
- ✅ Notifications persist across device reboot (OS-guaranteed)
|
||
- ✅ No force stop equivalent (iOS doesn't have user-facing force stop)
|
||
|
||
**iOS Challenges**:
|
||
- ❌ App code does NOT run when notification fires (only if user taps)
|
||
- ❌ Background execution severely limited (BGTaskScheduler only)
|
||
- ❌ Cannot rely on background execution for recovery
|
||
- ❌ Must detect missed notifications on app launch
|
||
|
||
**Platform Reference**: See [Platform Capability Reference §3](./alarms/01-platform-capability-reference.md#3-ios-notification-capability-matrix) for complete iOS behavior matrix.
|
||
|
||
### 2.2 Recovery Scenario Mapping
|
||
|
||
**Android → iOS Mapping**:
|
||
|
||
| Android Scenario | iOS Equivalent | Detection Method |
|
||
| ---------------- | -------------- | --------------- |
|
||
| `COLD_START` | App Launch After Termination | Check if notifications exist vs DB state |
|
||
| `FORCE_STOP` | App Terminated by System | Check if notifications missing vs DB state |
|
||
| `BOOT` | Device Reboot | BGTaskScheduler registration (Phase 3) |
|
||
| `WARM_START` | App Resume (Foreground) | Check app state on resume |
|
||
|
||
**Note**: iOS doesn't have a user-facing "force stop" equivalent. System termination is detected by comparing UNUserNotificationCenter state with database state.
|
||
|
||
### 2.3 iOS APIs Used
|
||
|
||
**Notification Management**:
|
||
- `UNUserNotificationCenter.current()` - Notification center
|
||
- `UNUserNotificationCenter.getPendingNotificationRequests()` - Check scheduled notifications
|
||
- `UNUserNotificationCenter.add()` - Schedule notifications
|
||
|
||
**Background Tasks**:
|
||
- `BGTaskScheduler.shared` - Background task scheduler
|
||
- `BGTaskScheduler.register()` - Register background task handlers
|
||
- `BGAppRefreshTaskRequest` - Background fetch requests
|
||
|
||
**App Lifecycle**:
|
||
- `applicationWillTerminate` - App termination notification
|
||
- `applicationDidBecomeActive` - App foreground notification
|
||
- `applicationDidEnterBackground` - App background notification
|
||
|
||
---
|
||
|
||
## 3. Implementation: ReactivationManager (iOS)
|
||
|
||
**⚠️ Illustrative only** – See Phase 1 and Phase 2 directives for canonical implementation.
|
||
|
||
**ReactivationManager Responsibilities by Phase**:
|
||
|
||
| Phase | Responsibilities |
|
||
| ----- | ---------------- |
|
||
| 1 | Cold start only (missed detection + verify/reschedule future) |
|
||
| 2 | Adds termination detection & recovery |
|
||
| 3 | Background task registration & boot recovery |
|
||
|
||
**For implementation details, see**:
|
||
- [Phase 1: ReactivationManager creation](./ios-implementation-directive-phase1.md#2-implementation-reactivationmanager)
|
||
- [Phase 2: Termination detection](./ios-implementation-directive-phase2.md#2-implementation-termination-detection)
|
||
|
||
### 3.1 Create New File
|
||
|
||
**File**: `ios/Plugin/DailyNotificationReactivationManager.swift`
|
||
|
||
**Purpose**: Centralized recovery logic for app launch scenarios
|
||
|
||
### 3.2 Class Structure
|
||
|
||
**⚠️ Illustrative only** – See Phase 1 for canonical implementation.
|
||
|
||
```swift
|
||
import Foundation
|
||
import UserNotifications
|
||
|
||
/**
|
||
* Manages recovery of notifications on app launch
|
||
* Handles cold start, warm start, and termination recovery scenarios
|
||
*
|
||
* @author Matthew Raymer
|
||
* @version 1.0.0
|
||
*/
|
||
class DailyNotificationReactivationManager {
|
||
|
||
private static let TAG = "DNP-REACTIVATION"
|
||
private let notificationCenter = UNUserNotificationCenter.current()
|
||
private let database: DailyNotificationDatabase
|
||
private let storage: DailyNotificationStorage
|
||
|
||
init(database: DailyNotificationDatabase, storage: DailyNotificationStorage) {
|
||
self.database = database
|
||
self.storage = storage
|
||
}
|
||
|
||
/**
|
||
* Perform recovery on app launch
|
||
* Detects scenario (cold/warm/termination) and handles accordingly
|
||
*/
|
||
func performRecovery() async {
|
||
do {
|
||
NSLog("\(Self.TAG): Starting app launch recovery")
|
||
|
||
// Step 1: Detect scenario
|
||
let scenario = try await detectScenario()
|
||
NSLog("\(Self.TAG): Detected scenario: \(scenario)")
|
||
|
||
// Step 2: Handle based on scenario
|
||
switch scenario {
|
||
case .termination:
|
||
try await handleTerminationRecovery()
|
||
case .coldStart:
|
||
try await handleColdStartRecovery()
|
||
case .warmStart:
|
||
try await handleWarmStartRecovery()
|
||
case .none:
|
||
NSLog("\(Self.TAG): No recovery needed")
|
||
}
|
||
|
||
NSLog("\(Self.TAG): App launch recovery completed")
|
||
} catch {
|
||
NSLog("\(Self.TAG): Error during app launch recovery: \(error)")
|
||
}
|
||
}
|
||
|
||
// ... implementation methods below ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Recovery Scenario Detection
|
||
|
||
### 4.1 Scenario Detection Algorithm
|
||
|
||
**Platform Reference**: [iOS §3.1.1](./alarms/01-platform-capability-reference.md#311-notifications-survive-app-termination) - Notifications survive app termination
|
||
|
||
**Detection Logic**:
|
||
|
||
```swift
|
||
enum RecoveryScenario {
|
||
case none // No recovery needed (first launch or warm resume)
|
||
case coldStart // App launched after termination, notifications may exist
|
||
case termination // App terminated, notifications missing vs DB
|
||
case warmStart // App resumed from background (optimization only)
|
||
}
|
||
|
||
func detectScenario() async throws -> RecoveryScenario {
|
||
// Step 1: Check if database has schedules
|
||
let schedules = try database.getEnabledSchedules()
|
||
if schedules.isEmpty {
|
||
return .none // First launch
|
||
}
|
||
|
||
// Step 2: Get pending notifications from UNUserNotificationCenter
|
||
let pendingNotifications = try await notificationCenter.pendingNotificationRequests()
|
||
|
||
// Step 3: Compare DB state with notification center state
|
||
let dbNotificationIds = Set(schedules.flatMap { $0.getScheduledNotificationIds() })
|
||
let pendingIds = Set(pendingNotifications.map { $0.identifier })
|
||
|
||
// Step 4: Determine scenario
|
||
if pendingIds.isEmpty && !dbNotificationIds.isEmpty {
|
||
// DB has schedules but no notifications scheduled
|
||
return .termination
|
||
} else if !pendingIds.isEmpty && !dbNotificationIds.isEmpty {
|
||
// Both have data - check if they match
|
||
if dbNotificationIds != pendingIds {
|
||
return .coldStart // Mismatch indicates recovery needed
|
||
} else {
|
||
return .warmStart // Match indicates warm resume
|
||
}
|
||
}
|
||
|
||
return .none
|
||
}
|
||
```
|
||
|
||
**For complete implementation, see**: [Phase 1 directive](./ios-implementation-directive-phase1.md#3-scenario-detection)
|
||
|
||
---
|
||
|
||
## 5. Missed Notification Detection
|
||
|
||
### 5.1 Detection Logic
|
||
|
||
**Platform Reference**: [iOS §3.2.1](./alarms/01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) - App code does not run when notification fires
|
||
|
||
**iOS Behavior**: When a notification fires, the app code does NOT execute. The notification is displayed, but the app must detect missed notifications on the next app launch.
|
||
|
||
**Detection Steps**:
|
||
|
||
1. Query database for notifications with `scheduled_time < currentTime`
|
||
2. Filter for notifications with `delivery_status != 'delivered'`
|
||
3. Mark as `'missed'` in database
|
||
4. Record in history table
|
||
|
||
**For complete implementation, see**: [Phase 1 directive](./ios-implementation-directive-phase1.md#4-missed-notification-detection)
|
||
|
||
---
|
||
|
||
## 6. Background Task Registration
|
||
|
||
### 6.1 BGTaskScheduler Registration
|
||
|
||
**Platform Reference**: [iOS §3.1.3](./alarms/01-platform-capability-reference.md#313-background-tasks-for-prefetching) - Background tasks for prefetching
|
||
|
||
**iOS Limitation**: BGTaskScheduler cannot be used for critical scheduling. It's system-controlled and not guaranteed.
|
||
|
||
**Use Case**: BGTaskScheduler is used for:
|
||
- Prefetching content (not critical timing)
|
||
- Boot recovery (system may defer)
|
||
- Background maintenance (best effort)
|
||
|
||
**Registration Location**: `AppDelegate.swift` or `SceneDelegate.swift`
|
||
|
||
**For complete implementation, see**: [Phase 3 directive](./ios-implementation-directive-phase3.md#2-background-task-registration)
|
||
|
||
---
|
||
|
||
## 7. Testing Strategy
|
||
|
||
### 7.1 iOS Testing Tools
|
||
|
||
**Simulator Testing**:
|
||
- `xcrun simctl` - Simulator control
|
||
- Xcode Instruments - Performance profiling
|
||
- Console.app - System log viewing
|
||
|
||
**Device Testing**:
|
||
- Xcode Device Console - Real device logs
|
||
- Settings → Developer → Background App Refresh - Control background execution
|
||
|
||
### 7.2 Test Scenarios
|
||
|
||
**Phase 1 Tests**:
|
||
- Cold start recovery
|
||
- Missed notification detection
|
||
- Future notification verification
|
||
|
||
**Phase 2 Tests**:
|
||
- App termination detection
|
||
- Comprehensive recovery
|
||
- Multiple schedules recovery
|
||
|
||
**Phase 3 Tests**:
|
||
- Background task registration
|
||
- Boot recovery (simulated)
|
||
- Background task execution
|
||
|
||
**For complete test procedures, see**: [iOS Test Scripts](../test-apps/ios-test-app/test-phase1.sh)
|
||
|
||
---
|
||
|
||
## 8. Platform-Specific Notes
|
||
|
||
### 8.1 Notification Persistence
|
||
|
||
**iOS Advantage**: Notifications persist automatically across:
|
||
- App termination
|
||
- Device reboot (for calendar/time triggers)
|
||
|
||
**App Responsibility**: Must still:
|
||
- Detect missed notifications on app launch
|
||
- Reschedule future notifications if needed
|
||
- Track delivery status in database
|
||
|
||
### 8.2 Background Execution Limits
|
||
|
||
**iOS Limitation**: Background execution is severely limited:
|
||
- BGTaskScheduler is system-controlled
|
||
- Cannot rely on background execution for recovery
|
||
- Must handle recovery on app launch
|
||
|
||
**Workaround**: Use BGTaskScheduler for prefetching only, not for critical scheduling.
|
||
|
||
### 8.3 Timing Tolerance
|
||
|
||
**iOS Limitation**: Calendar-based notifications have ±180 second tolerance.
|
||
|
||
**Impact**: Notifications may fire up to 3 minutes early or late.
|
||
|
||
**Mitigation**: Account for tolerance in missed notification detection logic.
|
||
|
||
---
|
||
|
||
## 9. Next Steps
|
||
|
||
1. **Start with Phase 1**: Implement cold start recovery
|
||
- See [Phase 1 directive](./ios-implementation-directive-phase1.md)
|
||
- Focus on missed notification detection
|
||
- Verify future notifications are scheduled
|
||
|
||
2. **Proceed to Phase 2**: Add termination detection
|
||
- See [Phase 2 directive](./ios-implementation-directive-phase2.md)
|
||
- Implement comprehensive recovery
|
||
- Handle multiple schedules
|
||
|
||
3. **Complete Phase 3**: Background task registration
|
||
- See [Phase 3 directive](./ios-implementation-directive-phase3.md)
|
||
- Register BGTaskScheduler handlers
|
||
- Implement boot recovery
|
||
|
||
---
|
||
|
||
## 10. References
|
||
|
||
- [Platform Capability Reference](./alarms/01-platform-capability-reference.md) - iOS OS-level facts
|
||
- [Plugin Requirements](./alarms/03-plugin-requirements.md) - Requirements this directive implements
|
||
- [Android Implementation Directive](./android-implementation-directive.md) - Android equivalent for comparison
|
||
- [iOS Recovery Scenario Mapping](./ios-recovery-scenario-mapping.md) - Detailed scenario mapping
|
||
- [iOS Core Data Migration Guide](./ios-core-data-migration.md) - Database migration guide
|
||
|
||
---
|
||
|
||
**Document Version**: 1.0.0
|
||
**Last Updated**: 2025-12-08
|
||
**Next Review**: After Phase 1 implementation
|
||
|