Skip to content

Commit

Permalink
Merge pull request #8 from Ornament-Health/feat/statistics
Browse files Browse the repository at this point in the history
  • Loading branch information
Cirych authored May 18, 2023
2 parents cfc5780 + 6ed3cfd commit a91e3e2
Show file tree
Hide file tree
Showing 16 changed files with 2,094 additions and 16 deletions.
6 changes: 6 additions & 0 deletions RCTAppleHealthKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
3774C8D71D20C65F0000B3F3 /* RCTAppleHealthKit+Methods_Fitness.m in Sources */ = {isa = PBXBuildFile; fileRef = 3774C8D61D20C65F0000B3F3 /* RCTAppleHealthKit+Methods_Fitness.m */; };
377D44F31D247D0A004E35CB /* RCTAppleHealthKit+Methods_Characteristic.m in Sources */ = {isa = PBXBuildFile; fileRef = 377D44F21D247D0A004E35CB /* RCTAppleHealthKit+Methods_Characteristic.m */; };
37837E7D1DCFE270000201A0 /* RCTAppleHealthKit+Methods_Sleep.m in Sources */ = {isa = PBXBuildFile; fileRef = 37837E7C1DCFE270000201A0 /* RCTAppleHealthKit+Methods_Sleep.m */; };
40CA05B029C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CA05AF29C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.m */; };
4F8095C92523C39D00A84ADB /* RCTAppleHealthKit+Methods_LabTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F8095C82523C39D00A84ADB /* RCTAppleHealthKit+Methods_LabTests.m */; };
58C81E6F1F84F6970005DD48 /* RCTAppleHealthKit+Methods_Activity.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C81E6D1F84F6970005DD48 /* RCTAppleHealthKit+Methods_Activity.m */; };
61232F931E303758000A5026 /* RCTAppleHealthKit+Methods_Mindfulness.m in Sources */ = {isa = PBXBuildFile; fileRef = 61232F921E303758000A5026 /* RCTAppleHealthKit+Methods_Mindfulness.m */; };
Expand Down Expand Up @@ -66,6 +67,8 @@
377D44F21D247D0A004E35CB /* RCTAppleHealthKit+Methods_Characteristic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTAppleHealthKit+Methods_Characteristic.m"; sourceTree = "<group>"; };
37837E7B1DCFE270000201A0 /* RCTAppleHealthKit+Methods_Sleep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTAppleHealthKit+Methods_Sleep.h"; sourceTree = "<group>"; };
37837E7C1DCFE270000201A0 /* RCTAppleHealthKit+Methods_Sleep.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTAppleHealthKit+Methods_Sleep.m"; sourceTree = "<group>"; };
40CA05AE29C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.h"; sourceTree = "<group>"; };
40CA05AF29C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.m"; sourceTree = "<group>"; };
4F8095C72523C39D00A84ADB /* RCTAppleHealthKit+Methods_LabTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTAppleHealthKit+Methods_LabTests.h"; sourceTree = "<group>"; };
4F8095C82523C39D00A84ADB /* RCTAppleHealthKit+Methods_LabTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTAppleHealthKit+Methods_LabTests.m"; sourceTree = "<group>"; };
58C81E6D1F84F6970005DD48 /* RCTAppleHealthKit+Methods_Activity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RCTAppleHealthKit+Methods_Activity.m"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -154,6 +157,8 @@
8C79D95F268E081A00DBDC40 /* RCTAppleHealthKit+Methods_ClinicalRecords.m */,
136368C1272BE98800C9A95F /* RTCAppleHealthKit+Methods_Statistics.h */,
136368C2272BE98800C9A95F /* RTCAppleHealthKit+Methods_Statistics.m */,
40CA05AE29C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.h */,
40CA05AF29C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.m */,
ABA7B427B5187820CBE7BCD1 /* Types */,
ABA7B06B86046F15D272E16A /* RCTTypes.h */,
);
Expand Down Expand Up @@ -242,6 +247,7 @@
3774C8931D2092F20000B3F3 /* RCTAppleHealthKit.m in Sources */,
377D44F31D247D0A004E35CB /* RCTAppleHealthKit+Methods_Characteristic.m in Sources */,
4F8095C92523C39D00A84ADB /* RCTAppleHealthKit+Methods_LabTests.m in Sources */,
40CA05B029C08A07005C8255 /* RCTAppleHealthKit+RTCAppleHealthKit_Methods_MedianStatistics.m in Sources */,
3774C89E1D2095850000B3F3 /* RCTAppleHealthKit+TypesAndPermissions.m in Sources */,
3774C8D71D20C65F0000B3F3 /* RCTAppleHealthKit+Methods_Fitness.m in Sources */,
58C81E6F1F84F6970005DD48 /* RCTAppleHealthKit+Methods_Activity.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "RCTAppleHealthKit.h"

@interface RCTAppleHealthKit (Methods_MedianStatistics)

- (void)statistics_getMedianStatistic:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;

@end
192 changes: 192 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Methods_MedianStatistics.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#import "RCTAppleHealthKit+Methods_MedianStatistics.h"
#import "RCTAppleHealthKit+TypesForMedianStatistic.h"
#import "RCTAppleHealthKit+Queries.h"
#import "RCTAppleHealthKit+Utils.h"

#import <React/RCTLog.h>

@implementation RCTAppleHealthKit (RTCAppleHealthKit_Methods_MedianStatistics)

// MARK: - Public

- (void)statistics_getMedianStatistic:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback {
NSArray<__kindof NSString *> *types = [RCTAppleHealthKit typesFromOptions:input];
if (types.count == 0) {
callback(@[RCTMakeError(@"RNHealth: No data types provided", nil, nil)]);
return;
}

NSDate *startDate = [NSDate dateWithTimeIntervalSince1970:0];
NSDate *endDate = [NSDate date];

NSMutableArray *output = [NSMutableArray new];

// prepare object from input values
NSMutableDictionary *validSamples = [NSMutableDictionary new];
for(NSString *sampleName in types) {
HKSampleType *sampleValue =(HKSampleType *)[self getObjectFromText:sampleName];

if ([sampleValue isKindOfClass:[HKCharacteristicType class]]) {
NSLog(@"RNHealth: Could not load data for HKCharacteristicType: %@", sampleName);
continue;
}

if (sampleValue != nil) {
validSamples[sampleName] = sampleValue;
} else {
NSLog(@"RNHealth: Could not load data for type: %@", sampleName);
continue;
}
}

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 10;

NSOperation *doneOperation = [[NSOperation alloc] init];
[doneOperation setCompletionBlock:^{
callback(@[[NSNull null], output]);
return;
}];

for(NSString *sampleName in [validSamples allKeys]) {

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

HKSampleType *sample = validSamples[sampleName];

int limit = 5000;
__block HKQueryAnchor *anchor = nil;
__block NSMutableArray<__kindof HKSample *> *resultArray = [NSMutableArray new];
__block NSMutableArray<__kindof HKSample *> *samplesFirstByDate = [NSMutableArray new];
__block NSMutableArray<__kindof HKSample *> *samplesLastByDate = [NSMutableArray new];
__block BOOL hasResults = YES;
__block NSString *anchorString = nil;

while (hasResults) {
if (anchorString != nil) {
NSData* anchorData = [[NSData alloc] initWithBase64EncodedString:anchorString options:0];
anchor = [NSKeyedUnarchiver unarchiveObjectWithData:anchorData];
}

NSPredicate *predicate = [RCTAppleHealthKit predicateForAnchoredQueries:anchor startDate:startDate endDate:endDate];

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

[self fetchBatchOfSamples:sample
predicate:predicate
anchor:anchor
limit:limit
completion:^(NSDictionary *results, NSError *error) {

if (results) {
@try {

NSMutableArray<__kindof HKSample *> *data = results[@"data"];

if (data == nil) {
hasResults = NO;
NSLog(@"RNHealth getMedianStatistic: An error occured");
dispatch_semaphore_signal(semaphore);
}

if (([sample isKindOfClass:[HKWorkoutType class]]) && (![@"workout" isEqual:sampleName])) {
NSMutableArray<__kindof HKSample *> *dataByActivityType = [NSMutableArray new];

for(HKWorkout *sampleElement in data) {
NSString *type = [RCTAppleHealthKit stringForHKWorkoutActivityType:[sampleElement workoutActivityType]];

if ([type caseInsensitiveCompare:sampleName] == NSOrderedSame) {
[dataByActivityType addObject:sampleElement];
}
}

data = dataByActivityType;
}

if (data.count > 0) {
[resultArray addObjectsFromArray:data];

//re-assign anchor
anchorString = results[@"anchor"];

// find very first and last, and save them
HKSample *firstFromFetched = [RCTAppleHealthKit firstByDateFromSamples:data];
[samplesFirstByDate addObject:firstFromFetched];

HKSample *lastFromFetched = [RCTAppleHealthKit lastByDateFromSamples:data];
[samplesLastByDate addObject:lastFromFetched];

} else {
hasResults = NO;
}
dispatch_semaphore_signal(semaphore);
} @catch (NSException *exception) {
hasResults = NO;
NSLog(@"RNHealth getMedianStatistic: An error occured");
dispatch_semaphore_signal(semaphore);
}
} else {
hasResults = NO;
NSLog(@"RNHealthgetMedianStatistic: An error occured");
dispatch_semaphore_signal(semaphore);
}
}];

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
};

if (resultArray.count != 0) {

NSMutableArray *intervalsInSecond = [NSMutableArray new];
NSMutableArray *intervalsInDays = [NSMutableArray new];
int i = 0;
while (i < resultArray.count - 1)
{
HKSample *first = resultArray[i];
HKSample *second = resultArray[i + 1];

NSDate *startInterval = second.startDate;
NSDate *endInterval = first.endDate;

// count median for array of interval in seconds
NSTimeInterval secondsBetween = fabs([endInterval timeIntervalSinceDate:startInterval]);
NSNumber *seconds = [NSNumber numberWithDouble:secondsBetween];
[intervalsInSecond addObject:seconds];

// count median for array of interval in days
NSNumber *days = [NSNumber numberWithInteger:[RCTAppleHealthKit daysFromSeconds:startInterval endDate:endInterval]];
[intervalsInDays addObject:days];

i++;
};

NSNumber *medianIntervalInSeconds = [RCTAppleHealthKit calculateMedian:intervalsInSecond];
NSNumber *medianIntervalInDays = [RCTAppleHealthKit calculateMedian:intervalsInDays];

HKSample *firstSample = [RCTAppleHealthKit firstByDateFromSamples:samplesFirstByDate];
HKSample *lastSample = [RCTAppleHealthKit lastByDateFromSamples:samplesLastByDate];

NSString *resultStartDateString = [RCTAppleHealthKit buildISO8601StringFromDate:firstSample.startDate];
NSString *resultEndDateString = [RCTAppleHealthKit buildISO8601StringFromDate:lastSample.endDate];

NSMutableDictionary *response = [NSMutableDictionary dictionary];
response[@"parameterName"] = sampleName ? sampleName : @"";
response[@"firstEntry"] = resultStartDateString ? resultStartDateString : @"";
response[@"lastEntry"] = resultEndDateString ? resultEndDateString : @"";
response[@"entryCount"] = @(resultArray.count);
response[@"medianSeconds"] = medianIntervalInSeconds ? medianIntervalInSeconds : [NSNull null];
response[@"medianDays"] = medianIntervalInDays ? medianIntervalInDays : [NSNull null];

[output addObject:response];
}

}];
[queue addOperation:operation];
[doneOperation addDependency:operation];
}

[queue addOperation:doneOperation];

}

@end
5 changes: 5 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,10 @@
ascending:(BOOL)asc
limit:(NSUInteger)lim
completion:(void (^)(NSArray *, NSError *))completion;
- (void)fetchBatchOfSamples:(HKSampleType *)type
predicate:(NSPredicate *)predicate
anchor:(HKQueryAnchor *)anchor
limit:(NSUInteger)lim
completion:(void (^)(NSDictionary *, NSError *))completion;

@end
44 changes: 44 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Queries.m
Original file line number Diff line number Diff line change
Expand Up @@ -1171,4 +1171,48 @@ - (void)fetchStatisticsSamplesOfType:(HKQuantityType *)quantityType
[self.healthStore executeQuery:query];
}

- (void)fetchBatchOfSamples:(HKSampleType *)type
predicate:(NSPredicate *)predicate
anchor:(HKQueryAnchor *)anchor
limit:(NSUInteger)lim
completion:(void (^)(NSDictionary *, NSError *))completion {

// declare the block
void (^handlerBlock)(HKAnchoredObjectQuery *query, NSArray<__kindof HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error);

// create and assign the block
handlerBlock = ^(HKAnchoredObjectQuery *query, NSArray<__kindof HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {

dispatch_async(dispatch_get_main_queue(), ^{

if (error) {
NSLog(@"RNHealth: An error occured for %@: %@", type.description, error.description);
if (completion) {
completion(nil, error);
}
return;
}

if (completion) {

NSData *anchorData = [NSKeyedArchiver archivedDataWithRootObject:newAnchor];
NSString *anchorString = [anchorData base64EncodedStringWithOptions:0];

NSMutableDictionary *response = [NSMutableDictionary dictionary];
response[@"anchor"] = anchorString ? anchorString : @"";
response[@"data"] = sampleObjects;

completion(response, error);
}
});
};
HKAnchoredObjectQuery *query = [[HKAnchoredObjectQuery alloc] initWithType:type
predicate:predicate
anchor:anchor
limit:lim
resultsHandler:handlerBlock];

[self.healthStore executeQuery:query];
}

@end
1 change: 1 addition & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- (NSDictionary *)readPermsDict;
- (NSDictionary *)writePermsDict;
- (NSSet *)getReadPermsFromOptions:(NSArray *)options;
- (nullable HKObjectType *)getReadPermFromText:(nonnull NSString*)key;
- (NSSet *)getWritePermsFromOptions:(NSArray *)options;
- (HKObjectType *)getWritePermFromString:(NSString *)string;
- (NSString *)getAuthorizationStatusString:(HKAuthorizationStatus)status;
Expand Down
7 changes: 7 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+TypesForMedianStatistic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "RCTAppleHealthKit.h"

@interface RCTAppleHealthKit (TypesForMedianStatistic)

- (nullable HKSampleType *)getObjectFromText:(nonnull NSString*)key;

@end
Loading

0 comments on commit a91e3e2

Please sign in to comment.