Healthkit queries dont work when the app is in background

374 Views Asked by At

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

}

1

There are 1 best solutions below

2
On

Try adding background modes capabilities, and add checkboxes Background processing, and background fetch. Hope it works.