HealthKit (iOS) won't deliver data in background (objC)
Asked Answered
K

2

15

We're currently trying to get HealthKit to work in the background, in order to deliver steps data to our server when the App is closed.

For experimental purposes we've created a brand new iOS project in XCode, enabled HealhtKit and all background modes in Compabilities. After that, we pretty much run the code (see further down).

So what happens first is that the app ofcourse asks for the permissions, which we grant. What we're expecting is that the app should keep deliver the steps data every hour, to the server. But it doesnt do that, it seems like the app cant do anything when it's not active.

The app only deliver data when it gets resumed or started, but not at all from the background (Soft-closed / Hard-closed)

appdelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self setTypes];
    return YES;
}


-(void) setTypes
{
    self.healthStore = [[HKHealthStore alloc] init];

    NSMutableSet* types = [[NSMutableSet alloc]init];
    [types addObject:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]];

    [self.healthStore requestAuthorizationToShareTypes: types
                                             readTypes: types
                                            completion:^(BOOL success, NSError *error) {

                                                dispatch_async(dispatch_get_main_queue(), ^{
                                                    [self observeQuantityType];
                                                    [self enableBackgroundDeliveryForQuantityType];
                                                });
                                            }];
}

-(void)enableBackgroundDeliveryForQuantityType{
    [self.healthStore enableBackgroundDeliveryForType: [HKQuantityType quantityTypeForIdentifier: HKQuantityTypeIdentifierStepCount] frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError *error) {
    }];
}


-(void) observeQuantityType{

    HKSampleType *quantityType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

    HKObserverQuery *query =
    [[HKObserverQuery alloc]
     initWithSampleType:quantityType
     predicate:nil
     updateHandler:^(HKObserverQuery *query,
                     HKObserverQueryCompletionHandler completionHandler,
                     NSError *error) {

         dispatch_async(dispatch_get_main_queue(), ^{
             if (completionHandler) completionHandler();
             [self getQuantityResult];

         });
     }];
    [self.healthStore executeQuery:query];
}


-(void) getQuantityResult{

    NSInteger limit = 0;
    NSPredicate* predicate = nil;

    NSString *endKey =  HKSampleSortIdentifierEndDate;
    NSSortDescriptor *endDate = [NSSortDescriptor sortDescriptorWithKey: endKey ascending: NO];

    HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType: [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]
                                                           predicate: predicate
                                                               limit: limit
                                                     sortDescriptors: @[endDate]
                                                      resultsHandler:^(HKSampleQuery *query, NSArray* results, NSError *error){

                                                          dispatch_async(dispatch_get_main_queue(), ^{
                                                                // sends the data using HTTP
                                                              [self sendData: [self resultAsNumber:results]];

                                                          });
                                                      }];
    [self.healthStore executeQuery:query];
}
Kalvin answered 21/12, 2014 at 20:22 Comment(17)
I took your sample code and modified it so that it gives me a local notification rather than sending data to a server (since I don't have server). It is working, but I made a few other changes so I don't know which one(s) are necessary. The changes I made are - get rid of all of the dispatch_async on main queue. Set a predicate to only deliver todays steps (retrieving all steps took a long time). Moved the call to completionHandler() to the end of the resultsHandler in getQuantityResult - so that it is called once all processing is finishedOptime
Hi Paulw11, thanks for your reply. Are you sure that you get results while in background, e.g still after 1 hour? We get results, but only for like 1-2 min, then nothing. How about you?Kalvin
Hmm this is weird, we can get it to work in the simulator, but not on a physical iPhone device. We are running out of clues, does anyone know what to look for? Since it works in the simulator..Kalvin
Yes, definitely after a few hours. I also added the background fetch entitlement - not sure if that matters.Optime
It is really odd.. Here's our complete AppDelegate.m code: pastebin.com/J5RaQjDZ We've tried everything, but nothing seems to work.. Sorry, I dont like to ask you for too much, but if you just browse the code really quickly, do you notice something odd?Kalvin
And just to be clear, yo do run this on your phone, not only in the simulator?Kalvin
Yes, on my phone. I Did notice that it didn't necessarily deliver every hour if the phone was just sitting - I had to be walking around and actually generating steps countOptime
That is the goal we are aiming for, the possibility to push data to the server when steps are added. We are trying with adding steps manually (Add Data Point) but it works for aprox 30 secs, then nothing happens.. Anyway, did you see our Pastebin-URL if something looks funny? pastebin.com/J5RaQjDZKalvin
The only real difference I can see is that I passed the completionHandler to getQuantityResult so that I could invoke it inside the resultsHandler block - i.e. when the results have actually been retrieved. Here is my code - gist.github.com/paulw11/a792d1f8645723d9b48fOptime
Did any of you find a working solution? I ran Paulw11's code on iOS 8.1 on an iPhone 6+, enabled background modes for background fetch and remote notifications. Notifications only fired when I re-opened the app. But no step-related notifications were fired while the app was in the background.Mccusker
Hi, sorry, I thought I answered. The exact code pasted above from Paulw11 helped me solve everything. Since it's not an answer I couldnt mark is as the best answer.Kalvin
We recieved updates (if we are lucky) once every hour. Sometimes multiple updates were sent at once. Every time I open the app, updates were sent also. We've enabled pretty much all background modes too. Dont know if that's the case thoughKalvin
Thanks for confirming that. But every hour? Ouch... Wish it wasn't so infrequent. I was expecting frequent (every minute or less) updates.Mccusker
Evert hour if you are lucky :-) sometimes multiple updates gets fired at once, and some hours gets no updates. I guess Apple will work a lot on this in the future. At least it's workingKalvin
I was able to get background updating to work on my phone even when the app was terminated. These were weight samples added to the HK store, but I imagine it would work with steps as well. #28458731Tessler
@Kalvin I just release an answer regarding to your problem using Swift , maybe can help you #26376267Stamford
Look at my answer here: https://mcmap.net/q/363332/-healthkit-background-delivery-when-app-is-not-runningCarden
E
0

I see something that might be causing an issue in your AppDelegate, particularly this line:

[[NSURLConnection alloc] initWithRequest:request delegate:self];

This is creating an NSURLConnection, but not starting it. Try changing it to this:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];

Edit: After taking a second look at the docs

They recommend setting up your observer queries in your application didFinishLaunchingWithOptions: method. In your code above, you set the HKObserverQuery up in the authorization handler, which is called on a random background queue. Try making this change to set it up on the main thread:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self setTypes];
    [self observeQuantityType];
    return YES;
}

HKObserverQuery Reference

Elle answered 15/5, 2015 at 21:43 Comment(0)
O
5

I found this out a little while ago when talking to someone from Apple. Apparently you can't access HK data in the background if the device is locked:

NOTE

Because the HealthKit store is encrypted, your app cannot read data from the store when the phone is locked. This means your app may not be able to access the store when it is launched in the background. However, apps can still write data to the store, even when the phone is locked. The store temporarily caches the data and saves it to the encrypted store as soon as the phone is unlocked.

from: https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/

Oribella answered 28/6, 2015 at 22:18 Comment(0)
E
0

I see something that might be causing an issue in your AppDelegate, particularly this line:

[[NSURLConnection alloc] initWithRequest:request delegate:self];

This is creating an NSURLConnection, but not starting it. Try changing it to this:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];

Edit: After taking a second look at the docs

They recommend setting up your observer queries in your application didFinishLaunchingWithOptions: method. In your code above, you set the HKObserverQuery up in the authorization handler, which is called on a random background queue. Try making this change to set it up on the main thread:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self setTypes];
    [self observeQuantityType];
    return YES;
}

HKObserverQuery Reference

Elle answered 15/5, 2015 at 21:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.