Hi,
I'm trying to implement HealthKit Background delivery for my app and I'm getting a strange behavior where the HKObserverQuery updateHandler is getting fired twice. I'm running Xcode 13.4 & iOS 15.5.
2022-06-10 10:05:58.725006+0200 Cori[79737:8949689] [HealthKit] Run Background Delivery Handler
2022-06-10 10:05:58.726283+0200 Cori[79737:8949689] [HealthKit] Run Background Delivery Handler
2022-06-10 10:05:58.736475+0200 Cori[79737:8949700] [HealthKit] New data: [200 mg/dL 4680E3F5-034A-4AED-843C-9066532AD459 "Salud" (15.5), "iPhone14,2" (15.5)metadata: {
2022-06-10 10:05:58.736632+0200 Cori[79737:8949700] [HealthKit] 1 new HKQuantityTypeIdentifierBloodGlucose samples
2022-06-10 10:05:58.736926+0200 Cori[79737:8949689] [persistence] HealthKit: Start import to Core Data
2022-06-10 10:05:58.737116+0200 Cori[79737:8949700] [HealthKit] New data: [200 mg/dL 4680E3F5-034A-4AED-843C-9066532AD459 "Salud" (15.5), "iPhone14,2" (15.5)metadata: {
2022-06-10 10:05:58.737985+0200 Cori[79737:8949689] [persistence] HealthKit: 1 samples of type HKQuantityTypeIdentifierBloodGlucose not from this app!
2022-06-10 10:05:58.753583+0200 Cori[79737:8949689] [persistence] HealthKit: Start batch insert request.
2022-06-10 10:05:58.753630+0200 Cori[79737:8949700] [HealthKit] 1 new HKQuantityTypeIdentifierBloodGlucose samples
2022-06-10 10:05:58.753873+0200 Cori[79737:8949689] [persistence] HealthKit: Start import to Core Data
2022-06-10 10:05:58.754019+0200 Cori[79737:8949689] [persistence] HealthKit: 1 samples of type HKQuantityTypeIdentifierBloodGlucose not from this app!
2022-06-10 10:05:58.754076+0200 Cori[79737:8949689] [persistence] HealthKit: Start batch insert request.
2022-06-10 10:05:58.759208+0200 Cori[79737:8949701] [persistence] HealthKit: Successfully imported data.
2022-06-10 10:05:58.759669+0200 Cori[79737:8949701] [persistence] HealthKit: Successfully imported data.
This is my code:
App Delegate
extension AppDelegate {
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if settings.syncHealthKit {
HealthKit.shared.setUpBackgroundDelivery()
}
return true
}
HealthKit Class
class HealthKit: ObservableObject {
private let logger = Logger(subsystem: "com.ChubbyApps.Diabetes", category: "HealthKit")
private var dataController: DataController = .shared
private var ajustes: AjustesModel = .shared
public let isAvailable = HKHealthStore.isHealthDataAvailable()
private lazy var isAuthorized = false
// MARK: - Properties
private var anchor: HKQueryAnchor? {
get {
guard let data = NSUbiquitousKeyValueStore.default.object(forKey: Keys.HKAnchor) as? Data else {
return nil
}
do {
return try NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: data)
} catch {
logger.error("Unable to unarchive \(data): \(error.localizedDescription)")
return nil
}
}
set(newAnchor) {
guard let newAnchor = newAnchor else {
return
}
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: newAnchor, requiringSecureCoding: true)
NSUbiquitousKeyValueStore.default.set(data, forKey: Keys.HKAnchor)
} catch {
logger.error("Unable to archive \(newAnchor): \(error.localizedDescription)")
}
}
}
// MARK: - Initializers
static let shared = HealthKit()
// MARK: - Public Methods
public func requestAuthorization() async -> Bool {
guard isAvailable else { return false }
do {
try await HKStore.requestAuthorization(toShare: types, read: types)
self.isAuthorized = true
return true
} catch let error {
self.logger.error("An error occurred while requesting HealthKit Authorization: \(error.localizedDescription)")
return false
}
}
// MARK: - Background
func setUpBackgroundDelivery() {
let query: HKObserverQuery = HKObserverQuery(sampleType: glucose, predicate: nil, updateHandler: self.backgroundDeliveryHandler)
HKStore.execute(query)
HKStore.enableBackgroundDelivery(for: glucose, frequency: .immediate) { success, error in
if success {
self.logger.debug("Enabled background delivery of stepcount changes")
} else {
if let theError = error {
self.logger.debug("Failed to enable background delivery of stepcount changes. Error: \(theError.localizedDescription)")
}
}
}
}
func backgroundDeliveryHandler(query: HKObserverQuery!, completionHandler: HKObserverQueryCompletionHandler!, error: Error!) {
self.logger.debug("Run Background Delivery Handler")
self.anchoredQueryFor(types)
completionHandler()
}
private func anchoredQueryFor(_ tipos: Set<HKSampleType>) {
var queryDescriptors = [HKQueryDescriptor]()
for type in tipos {
queryDescriptors.append(HKQueryDescriptor(sampleType: type, predicate: nil))
}
let anchoredQuery = HKAnchoredObjectQuery(
queryDescriptors: queryDescriptors,
anchor: anchor,
limit: HKObjectQueryNoLimit) { _, newSamples, _, newAnchor, error in
if let error = error {
self.logger.error("HKAnchoredObjectQuery error: \(error.localizedDescription)")
return
}
self.anchor = newAnchor
guard let samples = newSamples as? [HKQuantitySample] else { return }
guard !samples.isEmpty else { return }
self.logger.debug("New data: \(samples.debugDescription)")
for type in types {
let filteredSamples = samples.filter({ $0.quantityType == type })
if !filteredSamples.isEmpty {
self.logger.debug("\(filteredSamples.count) new \(type.debugDescription) samples")
Task {
do {
try await self.dataController.importHealthKitSample(filteredSamples, type: type)
} catch {
self.logger.error("importHealthKitSample error: \(error.localizedDescription)")
}
}
}
}
}
HKStore.execute(anchoredQuery)
}