0

I have followed Apple Docs and Several threads on stackoverflow on how to achieve background fetching of data from Health Store. So far I have:

  • Added HealthKit Entitlement to my appID
  • Added Required Background Modes
  • Added the code to AppDelegate.swift as Apple suggest (the snippet below is not following OOP just for facility to state this way here)

This is my code (swift): If your answer is in Obj-C and works, please state it as well, I will have to translate it, but that's no problem.

AppDelegate.swift

var healthStore: HKHealthStore?
var bpmSamples: [HKQuantitySample]?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    let dataTypesToWrite = [ ]
    let dataTypesToRead = [
        HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate),
        HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMassIndex),
        HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth)
    ]
    if self.healthStore == nil {
        self.healthStore = HKHealthStore()
    }
    self.healthStore?.requestAuthorizationToShareTypes(NSSet(array: dataTypesToWrite as [AnyObject]) as Set<NSObject>,
        readTypes: NSSet(array: dataTypesToRead) as Set<NSObject>, completion: {
            (success, error) in
            if success {
                self.addQueryObserver()
                println("User completed authorisation request.")
            } else {
                println("The user cancelled the authorisation request. \(error)")
            }
    })
    return true
}

func addQueryObserver(){
    let sampleType =
    HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)

    let query = HKObserverQuery(sampleType: sampleType, predicate: nil) {
        query, completionHandler, error in
        if error != nil {
            // Perform Proper Error Handling Here...
            println("*** An error occured while setting up the stepCount observer. \(error.localizedDescription) ***")
            abort()
        }
        println("query is running")

        self.performQueryForHeartBeatSamples()
        completionHandler()
    }
    healthStore?.executeQuery(query)
    healthStore?.enableBackgroundDeliveryForType(sampleType, frequency:.Immediate, withCompletion:{
        (success:Bool, error:NSError!) -> Void in
        let authorized = self.healthStore!.authorizationStatusForType(sampleType)
        println("HEALTH callback success", success)
        println("HEALTH callback authorized", sampleType)
    })
    if HKHealthStore.isHealthDataAvailable() == false {
        println("HEALTH data not available")
        return
    } else {
        println("HEALTH OK")
        self.performQueryForHeartBeatSamples()
    }
}
 // MARK: - HealthStore utility methods
func performQueryForHeartBeatSamples() {
    let endDate = NSDate()
    let startDate = NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitMonth, value: -2, toDate: endDate, options: nil)

    var heartRate : HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)

    let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
    let query = HKSampleQuery(sampleType: heartRate, predicate: predicate, limit: 0, sortDescriptors: nil, resultsHandler: {
        (query, results, error) in
        if results == nil {
            println("There was an error running the query: \(error)")
        }
        dispatch_async(dispatch_get_main_queue()) {
            self.bpmSamples = results as? [HKQuantitySample]
            let heartRateUnit: HKUnit = HKUnit.countUnit().unitDividedByUnit(HKUnit.minuteUnit())
            if self.bpmSamples?.count > 0 {
                if let sample = self.bpmSamples?[self.bpmSamples!.count - 1] {
                    println(sample.quantity!.description)
                    let quantity = sample.quantity
                    var value = quantity.doubleValueForUnit(heartRateUnit)
                    println("bpm: \(value)")
                }
            }
            else {
                println("No Data")
            }
        }
    })
    self.healthStore?.executeQuery(query)
}

So, the problem is that I only receive updates when I resume my app from background to active state manually.. HKObserverQuery doesn't seems to be working for me while on background mode.

Any suggestions?

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
Hugo Alonso
  • 6,684
  • 2
  • 34
  • 65
  • Are you debugging on a device and have you manually simulated a background fetch from the Debug menu? – Brandon Shega May 29 '15 at 20:03
  • I have tested it on both, real and simulator devices, in both cases I'm adding new data manually into HealthKit and waited for the `HKObserverQuery` to do its mojo. I just now tested that background fetch and it did nothing. – Hugo Alonso May 29 '15 at 20:14
  • It won't work on a simulator, you have to simulate the background fetch on a real device, did you try that? – Brandon Shega May 29 '15 at 20:19
  • The problem appears to be that when I enter either the Health App or my App, it forces the Health app to refresh the data. While my app is open Health app loads data only when it thinks is right to do it (not when I'm asking HealthKit to do it) ..so..my requests for data comes sadly out of sync until Health app decides to refresh or data arrives from the Watch that can be a loooong time after the data is taken from the sensors on such Apple Watch. Either way, it's just not the way it should be, as of the developer point of view. I will be testing on Watch OS 2 soon. – Hugo Alonso Aug 03 '15 at 14:29
  • Did you find any solution @HugoAlonso? – Nikhil Manapure Dec 16 '16 at 10:17
  • I am facing same problem. – Nikhil Manapure Dec 16 '16 at 10:17

1 Answers1

1

In my experiences, frequency ".Immediate" doesn't work well. The handler of queries are inserted into background queue. If the matching samples are frequently added or iOS is busy, the immediate frequency doesn't work well.

In addition, you cannot use HKSampleQuery in HKObserverQuery. updateHandler of HKObserverQuery may work, but resultHandler of HKSampleQuery will not. The handler of an observer query can be executed in the background mode but the one of a sample query cannot be executed in the background mode.

You should know that ONLY HKObserverQuery can be used in the background

jeongmin.cha
  • 768
  • 8
  • 22
  • Where did you find this info: "You cannot use HKSampleQuery in HKObserverQuery" ? – user3352495 May 04 '17 at 20:15
  • @user3352495 It was too long ago, so I don't remember the exact position of the sentences. However, I remember that Only HKObserverQuery among all APIs about HealthKit can be used in the background. When OS version goes up, it may be different from the old one of HealthKit, but in Apple's developer spirit, they don't want an app to use background resources. Therefore, I think it may be same yet. – jeongmin.cha May 07 '17 at 19:10