BGTaskScheduler with

I’m trying to use BGProcessingTaskRequest to fetch step data in the background and send it. However, when I combine BGProcessingTaskRequest, HKObserverQuery, and healthStore.enableBackgroundDelivery, the results sometimes return zero. When I don’t schedule the BGProcessingTaskRequest, the data retrieved using HKObserverQuery and HKSampleQueryDescriptor is correct.

    // Register Smart Walking Sync Task
    func registerSmartWalkingSync() {
#if !targetEnvironment(simulator)
        BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTaskIdentifier.smartwalking.rawValue, using: nil) { task in
            guard let task = task as? BGProcessingTask else { return }
            self.handleSmartWalkingSync(task: task)
        }
#endif
    }
    
    func scheduleSmartWalkingSync(in seconds: TimeInterval? = nil, at date: Date? = nil) {
        
        let newRequest = BGProcessingTaskRequest(identifier: BGTaskIdentifier.smartwalking.rawValue)
        newRequest.requiresNetworkConnectivity = true
        newRequest.requiresExternalPower = false
        
        if let seconds = seconds {
            newRequest.earliestBeginDate = Date().addingTimeInterval(seconds)
        } else if let date = date {
            newRequest.earliestBeginDate = date
        }
        
        do {
            try BGTaskScheduler.shared.submit(newRequest)
            debugPrint("✅ [BGTasksManager] scheduled for Smart Walking Sync")
        } catch {
            FirebaseConnection.shared.recordException(error)
            debugPrint("❌ [BGTasksManager] error: \(error)")
        }
    }
    
    // Handle Smart Walking Sync Task
    func handleSmartWalkingSync(task: BGProcessingTask) {
        debugPrint("🔄 [BGTasksManager] sync \(task.identifier) sync started")
        scheduleSmartWalkingSync(in: SYNC_SMARTWALKING_TIME_INTERVAL)
        
        let queue = OperationQueue()
        let operation = HealthActivitiesOperation()
        
        operation.completionBlock = {
            Task {
                do {
                    try await operation.sync()
                    task.setTaskCompleted(success: !operation.isCancelled)
                    debugPrint("✅ [BGTasksManager] sync \(task.identifier) completed successfully")
                } catch {
                    FirebaseConnection.shared.recordException(error)
                    task.setTaskCompleted(success: false)
                    debugPrint("❌ [BGTasksManager] sync \(task.identifier) error: \(error)")
                }
            }
        }
        
        task.expirationHandler = {
            operation.cancel()
        }
        
        queue.addOperation(operation)
    }
    // MARK: - HealthKit  Background Delivery
    internal func enableBackgroundDeliveryForAllTypes() async throws {
        for type in allTypes.filter({ type in
            type != HKQuantityType(.heartRate)
        }) {
            try await healthStore.enableBackgroundDelivery(for: type, frequency: .daily)
        }
        debugPrint("✅ [HealthKitManager] Enable Background Delivery")
    }

    internal func observeHealthKitQuery(predicate: NSPredicate?) async throws -> Set<HKSampleType> {
        let queryDescriptors: [HKQueryDescriptor] = allTypes
            .map { type in
                HKQueryDescriptor(sampleType: type, predicate: predicate)
            }
        
        return try await withCheckedThrowingContinuation { continuation in
            var hasResumed = false
            
            let query = HKObserverQuery(queryDescriptors: queryDescriptors) { query, updatedSampleTypes, completionHandler, error in
                if hasResumed {
                    return
                }
                
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: updatedSampleTypes ?? [])
                }
                
                hasResumed = true
                completionHandler()
            }
            
            healthStore.execute(query)
        }
    }

    internal func getHealthActivity(by date: Date, predicate: NSCompoundPredicate, sampleTypes: Set<HKSampleType>) async throws -> HealthActivityData {

        var data = HealthActivityData(steps: 0, calories: 0, distance: 0.0, distanceCycling: 0.0, totalDuration: 0, date: date, heartRate: nil)

        for sampleType in sampleTypes {
            guard let quantityType = sampleType as? HKQuantityType else {
                continue
            }
            
            switch quantityType {
                
            case HKQuantityType(.stepCount):
                let stepCount = try await getDescriptor(
                    date: date,
                    type: quantityType
                ).result(for: healthStore)
                    .statistics(for: date)?.sumQuantity()?.doubleValue(for: HKUnit.count())
                
                data.steps = stepCount ?? 0.0
                
                // Calculate total duration using HKSampleQueryDescriptor
                let totalDurationDescriptor = HKSampleQueryDescriptor(
                    predicates: [.quantitySample(type: quantityType, predicate: predicate)],
                    sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)]
                )
                
                let stepSamples = try await totalDurationDescriptor.result(for: healthStore)
                data.totalDuration += stepSamples
                    .reduce(0) { $0 + $1.endDate.timeIntervalSince($1.startDate) } / 60.0
                
            default:
                debugPrint("Unknown quantity type")
            }
            
        }
        
        return data
    }

Is it possible to use these frameworks together (BGProcessingTaskRequest, HKObserverQuery, HKSampleQueryDescriptor)?

I don't see that using the frameworks together has anything wrong, except that, on watchOS, background tasks share a budget, which is four updates an hour, as long as it has a complication on the active watch face. See the API reference of enableBackgroundDelivery(for:frequency:withCompletion:) for the documentation.

the results sometimes return zero.

Does that happen when your device sleeps? The HealthKit store will be locked as soon as the device begins to sleep. Reading data when he store is locked will fails; writing will be fine because the data will be queued.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

BGTaskScheduler with
 
 
Q