I need to observe the heart rate data in Healthkit and then send a notification to the user accordingly. The code is working fine, but every time I have to bring the app to foreground to be able to get notifications. I need that working continuously. It seems like the queries are not working on the background.
I have checked almost everything online but couldn't find a solution for my need. Any help would be appreciated!
EDIT: The background modes is also implemented. I checked the background fetch. Still doesn't work...
I added snippets of my code below:
func requestHealthKitPermission(successCallback: @escaping () -> Void) {
let sampleType: Set<HKSampleType> = [HealthKitService.heartRateType]
let healthKitStore = HKHealthStore()
healthKitStore.requestAuthorization(toShare: sampleType, read: sampleType) { (success, error) in
if success {
successCallback()
self.startObservingNewHeartRates()
}
if let error = error {
print("Error requesting health kit authorization: \(error)")
}
}
}
func startObservingNewHeartRates() {
// open observer query
let query = HKObserverQuery(sampleType: HealthKitService.heartRateType,
predicate: nil) { (query, completionHandler, error) in
DispatchQueue.main.async {
self.updateHeartRates(completionHandler: completionHandler)
}
}
healthKitStore.execute(query)
// Enable background delivery
healthKitStore.enableBackgroundDelivery(for: HealthKitService.heartRateType, frequency: .immediate) { (success, error) in
if let error = error {
print("could not enable background delivery: \(error)")
}
if success {
print("background delivery enabled")
}
}
}
func updateHeartRates(completionHandler: @escaping () -> Void) {
var anchor: HKQueryAnchor?
if let data = UserDefaults.standard.object(forKey: Constants.UserDefaults.HEART_RATE_ANCHOR_KEY) as? Data {
anchor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: data)
}
let sampleType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: anchor, limit: HKObjectQueryNoLimit) { query, newSamples, deletedSamples, newAnchor, error in
self.handleNewHeartRates(new: newSamples!, deleted: deletedSamples!)
if let newAnchor = newAnchor {
let data = try? NSKeyedArchiver.archivedData(withRootObject: newAnchor, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: Constants.UserDefaults.HEART_RATE_ANCHOR_KEY)
print("Wrote new Anchor")
}
completionHandler()
}
healthKitStore.execute(anchoredQuery)
}
func handleNewHeartRates(new: [HKSample], deleted: [HKDeletedObject]) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .full
let mapped = new.map { (sample) -> String? in
guard let hrSample: HKQuantitySample = sample as? HKQuantitySample else { return nil }
let heartRate = hrSample.quantity.doubleValue(for: HealthKitService.heartRateUnit)
if heartRate >= 70 { // TODO: extract to Constants
DispatchQueue.main.async {
self.viewController?.setupHeartRateNotifications()
}
}
return "\(heartRate)bpm @ \(dateFormatter.string(from: hrSample.startDate)) "
}.compactMap { $0 }
print(" ❤❤❤❤❤ new heartRate")
mapped.forEach { (line) in
print(line)
}
if let latestRecording = new.last {
let formattedDate = dateFormatter.string(from: latestRecording.endDate)
UserDefaults.standard.set(formattedDate, forKey: Constants.UserDefaults.LATEST_HEART_RATE_RECORDING_DATE_KEY)
}
}
}
Try adding background modes capabilities, and add checkboxes Background processing, and background fetch. Hope it works.