My app is utilizing ActivityKit to provide a simple start time and runtime for a user timing their live poker session. It runs in the background just fine, and if the app is terminated the Live Activity ends, which is fine, the counter is still being tracked inside the app elsewhere.
The issue is that if the phone is rebooted while the Live Activity is active, it remains active when the phone is turned back on and this is causing all sorts of problems when the app gets reopened. I’d like for the Live Activity to just be killed in the event of a phone reboot or battery dying. How is this handled properly? Below is part of my relevant code for the TimerViewModel class.
class TimerViewModel: ObservableObject {
private var timer: Timer?
@Published var liveSessionStartTime: Date?
@Published var liveSessionTimer: String = "00:00"
@Published var activity: Activity<LiveSessionWidgetAttributes>? = nil
@Published var reBuyAmount: String = ""
@Published var initialBuyInAmount: String = ""
@Published var totalRebuys: [Int] = []
var totalBuyInForLiveSession: Int {
(Int(initialBuyInAmount) ?? 0) + rebuyTotalForSession
}
var rebuyTotalForSession: Int {
return totalRebuys.reduce(0,+)
}
static var isCounting: Bool {
UserDefaults.standard.object(forKey: "liveSessionStartTime") != nil
}
init() {
NotificationCenter.default.addObserver(self, selector: #selector(appDidResume), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate), name: UIApplication.willTerminateNotification, object: nil)
// Attempt to recover liveSessionStartTime from UserDefaults
guard let startTime = UserDefaults.standard.object(forKey: "liveSessionStartTime") as? Date else {
print("No Live Session start time found.")
return
}
liveSessionStartTime = startTime
updateElapsedTime()
startUpdatingTimer()
initialBuyInAmount = UserDefaults.standard.string(forKey: "initialBuyInAmount") ?? ""
totalRebuys = UserDefaults.standard.array(forKey: "totalRebuys") as? [Int] ?? []
}
func startSession() {
let now = Date()
liveSessionStartTime = now
UserDefaults.standard.set(now, forKey: "liveSessionStartTime")
UserDefaults.standard.set(initialBuyInAmount, forKey: "initialBuyInAmount")
UserDefaults.standard.set(totalRebuys, forKey: "totalRebuys")
startUpdatingTimer()
scheduleUserNotification()
if ActivityAuthorizationInfo().areActivitiesEnabled {
let attributes = LiveSessionWidgetAttributes(eventDescription: "Live Session")
let state = LiveSessionWidgetAttributes.TimerStatus(startTime: now, elapsedTime: self.liveSessionTimer)
do {
activity = try Activity<LiveSessionWidgetAttributes>.request(attributes: attributes,
content: .init(state: state, staleDate: nil),
pushType: nil)
} catch {
print("Error: (error)")
}
}
}
}
When the user manually stops the session and ends the timer, this is the code that’s called to end the activity, which is working.
func stopTimer() {
timer?.invalidate()
UserDefaults.standard.removeObject(forKey: "liveSessionStartTime")
UserDefaults.standard.removeObject(forKey: "initialBuyInAmount")
UserDefaults.standard.removeObject(forKey: "totalRebuys")
// End Activity
Task {
await Activity<LiveSessionWidgetAttributes>.activities.first?.end(activity?.content, dismissalPolicy: .immediate)
}
cancelUserNotifications()
}
And finally, this snippet is what I thought would be able to handle the phone being rebooted in the event the app is terminated. However that doesn’t seem to be the case.
func endActivityKit() {
print("Ending Live Activities")
let semaphore = DispatchSemaphore(value: 0)
Task {
for activity in Activity<LiveSessionWidgetAttributes>.activities {
print("Ending Live Activity: (activity.id)")
await activity.end(nil, dismissalPolicy: .immediate)
}
semaphore.signal()
}
semaphore.wait()
}
@objc func applicationWillTerminate(_ application: UIApplication) {
print("Application will terminate...")
endActivityKit()
}