How to use updateHandler with HKAnchoredObjectQuery in iOS? - ios

In my application I want to fetch HealthKit data using HKAnchoredObjectQuery. I've written code which is returning added and deleted data but i want to set UpdateHandler with HKAnchoredObjectQuery so, when data added/deleted in HealthKit then I get notification in app.
-(void)AnchoredObjectQueryTest
{
HKSampleType *sampleType1 =
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
HKAnchoredObjectQuery *query =
[[HKAnchoredObjectQuery alloc]
initWithType:sampleType1
predicate:nil
anchor: HKAnchoredObjectQueryNoAnchor
limit:HKObjectQueryNoLimit
resultsHandler:^(HKAnchoredObjectQuery * query,
NSArray<HKSample *> * sampleObjects,
NSArray<HKDeletedObject *> * deletedObjects,
HKQueryAnchor *newAnchor,
NSError * error) {
if (error) {
// Perform proper error handling here...
NSLog(#"*** An error occured while performing the anchored object query. %# ***",
error.localizedDescription);
abort();
}
anchor = newAnchor;
for (HKQuantitySample *sample in sampleObjects) {
NSLog(#"Add : %#", sample);
}
for (HKDeletedObject *sample in deletedObjects) {
NSLog(#"Delete : %#", sample);
}
}];
[healthStore executeQuery:query];
}

Instantiate and execute your HKAnchoredObjectQuery and it will run once, calling back to the block specified in the handler parameter.
Instantiate the query and set the updateHandler property on the query, then execute the query. The query runs the first time as before, calling back to the handler parameter you provided at instantiation; the query runs subsequently when results are added or deleted to the store and calls back to your updateHandler.
In my case, I use the same block for the handler parameter and the updateHandler property.

Related

Realm-cocoa: How to add notification inside other notification block

In my app, I have registered a notification block that itself has to register an other notifiaction block.
notificationToken = [self.appState.currentProject addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
if (deleted) {
NSLog(#"The object was deleted.");
} else if (error) {
NSLog(#"An error occurred: %#", error);
} else {
bool filterChanged = false;
for (RLMPropertyChange * change in changes){
if([change.name isEqualToString:#"filters"])
filterChanged = true;
}
if(filterChanged){
// compute predicate from filters
// predicate = [NSPredicate ...
styles = [Style objectsWithPredicate:predicate]
notificationTokenStyles = [styles addNotificationBlock:^(RLMResults<Style *> * _Nullable results, RLMCollectionChange * _Nullable change, NSError * _Nullable error) {
// react on changed styles.
}]; // <- RLMException here
}
}];
}
}];
I know that this is technically not possible, but for me it is necessary. Can I somehow dispatch or defer the nested addNotificationBlock to another thread or so? I know I could wait 500 ms, but that would result in a race condition.
Now, my problem is that as soon as I perform a wirte transaction that leads to the first notification block to execute, I encounter RLMException: Cannot register notification blocks from within write transactions. just at the nested addNotificationBlock.
Thanks in advance!
This is a known problem (see this Realm issue on GitHub).
You can either wrap the creation of the new NotificationToken like this (Swift code):
DispatchQueue.main.async {
// register observation blocks here
}
Or you can call realm.refresh() before starting the write transaction.

HKObserverQuery in Health Kit

In which format does HKObserverQuery gets data?
There is no NSArray or NSDictionary to store data then how to get.
I am putting the query put not able to get all data, anyone please help?
HKObserverQuery *query =
[[HKObserverQuery alloc]
initWithSampleType:object
predicate:Predicate
updateHandler:^(HKObserverQuery *query,
HKObserverQueryCompletionHandler completionHandler,
NSError *error) {
[HKUnit unitFromString:#"count/min"]];
if (error) {
// Perform Proper Error Handling Here...
NSLog(#"*** An error occured while setting up the stepCount observer. %# ***",
error.localizedDescription);
abort();
}
else{
NSLog(#"#%",query);
}
}];
HKObserverQuery is not intended to deliver data to your app. The updateHandler is simply called whenever a HKSample matching your predicate is added or removed from HealthKit. It is then up to your app to perform additional queries in response, such as HKSampleQuery or HKStatisticsQuery.

Exception when trying to convert HeartRate from HealthKitStore

I am working on my first iPhone App: a simple app showing the heartRate results from HealthKit in a nice way. My first step is to show the results as a raw text. But unfortunately I'm getting an exception at the following line, telling me: "thread 1 signal SIGABRT". Does someone know, what I did wrong and hint me in a direction?
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
The rest of the code looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Set up an HKHealthStore, asking the user for read/write permissions. The profile view controller is the
// first view controller that's shown to the user, so we'll ask for all of the desired HealthKit permissions now.
// In your own app, you should consider requesting permissions the first time a user wants to interact with
// HealthKit data.
if ([HKHealthStore isHealthDataAvailable]) {
NSSet *writeDataTypes = [self dataTypesToWrite];
NSSet *readDataTypes = [self dataTypesToRead];
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error);
return;
}
}];
}
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
// Since we are interested in retrieving the user's latest sample
// we sort the samples in descending order by end date
// and set the limit to 1
// We are not filtering the data, and so the predicate is set to nil.
NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
// construct the query & since we are not filtering the data the predicate is set to nil
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:weightType predicate:nil limit:1 sortDescriptors:#[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
// if there is a data point, dispatch to the main queue
if (results) {
dispatch_async(dispatch_get_main_queue(), ^{
HKQuantitySample *quantitySample = results.firstObject;
// pull out the quantity from the sample
HKQuantity *quantity = quantitySample.quantity;
double usersBeatsPerMinute = [quantity doubleValueForUnit:[HKUnit countUnit]];
_HeartRateResults.text = [NSString stringWithFormat:#"%# lbs", [NSNumberFormatter localizedStringFromNumber:#(usersBeatsPerMinute) numberStyle:NSNumberFormatterNoStyle]];
});
}
}];
// do not forget to execute the query after its constructed
[_healthStore executeQuery:query];}
There was a comment in the documentation ("These samples use count/time units") I didn't quite understand, so I did a little searching and tried it out and was able to get a value I manually put into the Health app using this:
double rate = [mostRecentQuantity doubleValueForUnit:[[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
I haven't seen unitDividedByUnit before. Here's the article I pulled it from.

Watch os 2.0 beta: access heart beat rate

With Watch OS 2.0 developers are supposed to be allowed to access heart beat sensors....
I would love to play a bit with it and build a simple prototype for an idea I have, but I can't find anywhere info or documentation about this feature.
Can anyone point me on how to approach this task? Any link or info would be appreciated
Apple isn't technically giving developers access to the heart rate sensors in watchOS 2.0. What they are doing is providing direct access to heart rate data recorded by the sensor in HealthKit. To do this and get data in near-real time, there are two main things you need to do. First, you need to tell the watch that you are starting a workout (lets say you are running):
// Create a new workout session
self.workoutSession = HKWorkoutSession(activityType: .Running, locationType: .Indoor)
self.workoutSession!.delegate = self;
// Start the workout session
self.healthStore.startWorkoutSession(self.workoutSession!)
Then, you can start a streaming query from HKHealthKit to give you updates as HealthKit receives them:
// This is the type you want updates on. It can be any health kit type, including heart rate.
let distanceType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
// Match samples with a start date after the workout start
let predicate = HKQuery.predicateForSamplesWithStartDate(workoutStartDate, endDate: nil, options: .None)
let distanceQuery = HKAnchoredObjectQuery(type: distanceType!, predicate: predicate, anchor: 0, limit: 0) { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle when the query first returns results
// TODO: do whatever you want with samples (note you are not on the main thread)
}
// This is called each time a new value is entered into HealthKit (samples may be batched together for efficiency)
distanceQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle update notifications after the query has initially run
// TODO: do whatever you want with samples (note you are not on the main thread)
}
// Start the query
self.healthStore.executeQuery(distanceQuery)
This is all described in full detail in the demo at the end of the video What's New in HealthKit - WWDC 2015
You can get heart rate data by starting a workout and query heart rate data from healthkit.
Ask for premission for reading workout data.
HKHealthStore *healthStore = [[HKHealthStore alloc] init];
HKQuantityType *type = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKQuantityType *type2 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
HKQuantityType *type3 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
[healthStore requestAuthorizationToShareTypes:nil readTypes:[NSSet setWithObjects:type, type2, type3, nil] completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"health data request success");
}else{
NSLog(#"error %#", error);
}
}];
In AppDelegate on iPhone, respond this this request
-(void)applicationShouldRequestHealthAuthorization:(UIApplication *)application{
[healthStore handleAuthorizationForExtensionWithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"phone recieved health kit request");
}
}];
}
Then implement Healthkit Delegate:
-(void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"session error %#", error);
}
-(void)workoutSession:(HKWorkoutSession *)workoutSession didChangeToState:(HKWorkoutSessionState)toState fromState:(HKWorkoutSessionState)fromState date:(NSDate *)date{
dispatch_async(dispatch_get_main_queue(), ^{
switch (toState) {
case HKWorkoutSessionStateRunning:
//When workout state is running, we will excute updateHeartbeat
[self updateHeartbeat:date];
NSLog(#"started workout");
break;
default:
break;
}
});
}
Now it's time to write [self updateHeartbeat:date]
-(void)updateHeartbeat:(NSDate *)startDate{
__weak typeof(self) weakSelf = self;
//first, create a predicate and set the endDate and option to nil/none
NSPredicate *Predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:nil options:HKQueryOptionNone];
//Then we create a sample type which is HKQuantityTypeIdentifierHeartRate
HKSampleType *object = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
//ok, now, create a HKAnchoredObjectQuery with all the mess that we just created.
heartQuery = [[HKAnchoredObjectQuery alloc] initWithType:object predicate:Predicate anchor:0 limit:0 resultsHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
if (!error && sampleObjects.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//wait, it's not over yet, this is the update handler
[heartQuery setUpdateHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *SampleArray, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *Anchor, NSError *error) {
if (!error && SampleArray.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[SampleArray objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//now excute query and wait for the result showing up in the log. Yeah!
[healthStore executeQuery:heartQuery];
}
You also have a turn on Healthkit in capbilities. Leave a comment below if you have any questions.
You may use HKWorkout, which is part of the HealthKit framework.
Many of software kits for iOS are now available for watchOS, such as HealthKit. You can use HealthKit (HK) functions and classes in order to calculate burned calories, find heart rate, etc. You can use HKWorkout to calculate everything about workouts and access the related variables such as heart rate, just like you did with iOS before. Read developer documentations from Apple in order to learn about HealthKit. They can be found in developer.apple.com.

Deleting all HKQuantitySamples

I want to delete all HKQuantitySamples that my app has saved in the Health App for a certain HKQuantityType, how would I do that?
I can see the function
deleteObject:withCompletion:
in the Apple Documentation, but I do not really understand how to use it. Can somebody show an example maybe?
EDIT: I now use the following code for deleting:
I have saved my food information as a HKCorrelation and set my Local Food ID in the Correlations metadata HKMetadataKeyExternalUUID key.
For deletion I am fetching all the HKCorrelation objects between startDate and endDate and then if one of these fetched objects matches the Local Food ID I am looking for:
- I delete every object in that Correlation,
- Followed by deleting the Correlation itself
HKCorrelationType *foodType = [HKObjectType correlationTypeForIdentifier:HKCorrelationTypeIdentifierFood];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionNone];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:foodType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (!results) {
NSLog(#"An error occured fetching the user's tracked food. In your app, try to handle this gracefully. The error was: %#.", error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
for (HKCorrelation *foodCorrelation in results) {
if([[foodCorrelation.metadata valueForKey:HKMetadataKeyExternalUUID] isEqualToString:food_id_I_want_to_delete]) {
NSSet *objs = foodCorrelation.objects;
for (HKQuantitySample *sample in objs) {
[self.healthStore deleteObject:sample withCompletion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"Success. delete sample");
}
else {
NSLog(#"delete: An error occured deleting the sample. In your app, try to handle this gracefully. The error was: %#.", error);
}
}];
}
[self.healthStore deleteObject:foodCorrelation withCompletion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"Success. delete %#", [foodCorrelation.metadata valueForKey:HKMetadataKeyExternalUUID]);
}
else {
NSLog(#"delete: An error occured deleting the Correlation. In your app, try to handle this gracefully. The error was: %#.", error);
}
}];
return;
}
}
});
}];
[self.healthStore executeQuery:query];

Resources