Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
923 changes: 462 additions & 461 deletions RCTAppleHealthKit/RCTAppleHealthKit+Queries.m

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Utils.m
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,30 @@ + (HKSampleType *)quantityTypeFromName:(NSString *)type {
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
} else if ([type isEqual:@"BasalEnergyBurned"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBasalEnergyBurned];
} else if ([type isEqual:@"BodyMass"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
} else if ([type isEqual:@"BodyFatPercentage"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyFatPercentage];
} else if ([type isEqual:@"BodyTemperature"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];
} else if ([type isEqual:@"BloodGlucose"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodGlucose];
} else if ([type isEqual:@"BloodPressureDiastolic"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic];
} else if ([type isEqual:@"BloodPressureSystolic"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic];
} else if ([type isEqual:@"Cycling"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceCycling];
} else if ([type isEqual:@"HeartRate"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
} else if ([type isEqual:@"HeartRateVariabilitySDNN"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRateVariabilitySDNN];
} else if ([type isEqual:@"OxygenSaturation"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierOxygenSaturation];
} else if ([type isEqual:@"RestingHeartRate"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierRestingHeartRate];
} else if ([type isEqual:@"RespiratoryRate"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierRespiratoryRate];
} else if ([type isEqual:@"Running"]){
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
} else if ([type isEqual:@"StairClimbing"]) {
Expand Down
4 changes: 3 additions & 1 deletion RCTAppleHealthKit/RCTAppleHealthKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@

@property (nonatomic) HKHealthStore *healthStore;
@property (nonatomic, assign) BOOL hasListeners;
@property (nonatomic, assign) BOOL isLegacyBackgroundImplementation;

- (HKHealthStore *)_initializeHealthStore;
- (void)isHealthKitAvailable:(RCTResponseSenderBlock)callback;
- (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)getModuleInfo:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)getAuthorizationStatus:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)initializeBackgroundObservers:(RCTBridge *)bridge;
- (void)emitEventWithName:(NSString *)name andPayload:(NSDictionary *)payload;
- (void)initializeBackgroundObservers:(RCTBridge *)bridge isLegacyBackgroundImplementation:(BOOL)isLegacy;
- (void)emitEventWithName:(NSString *)name body:(NSString *)body;

@end
139 changes: 85 additions & 54 deletions RCTAppleHealthKit/RCTAppleHealthKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

@implementation RCTAppleHealthKit

bool hasListeners;
static bool hasListeners;
static bool isLegacyBackgroundImplementation;

RCT_EXPORT_MODULE();

Expand Down Expand Up @@ -327,12 +328,12 @@ + (BOOL)requiresMainQueueSetup

RCT_EXPORT_METHOD(getEnergyConsumedSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
{
[self dietary_getEnergyConsumedSamples:input callback:callback];
[self dietary_getEnergyConsumedSamples:input callback:callback];
}

RCT_EXPORT_METHOD(getProteinSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
{
[self dietary_getProteinSamples:input callback:callback];
[self dietary_getProteinSamples:input callback:callback];
}

RCT_EXPORT_METHOD(getFiberSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
Expand All @@ -342,7 +343,7 @@ + (BOOL)requiresMainQueueSetup

RCT_EXPORT_METHOD(getTotalFatSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
{
[self dietary_getTotalFatSamples:input callback:callback];
[self dietary_getTotalFatSamples:input callback:callback];
}

RCT_EXPORT_METHOD(saveFood:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
Expand Down Expand Up @@ -395,8 +396,8 @@ + (BOOL)requiresMainQueueSetup

RCT_EXPORT_METHOD(getActiveEnergyBurned:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
{
[self _initializeHealthStore];
[self activity_getActiveEnergyBurned:input callback:callback];
[self _initializeHealthStore];
[self activity_getActiveEnergyBurned:input callback:callback];
}

RCT_EXPORT_METHOD(getBasalEnergyBurned:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
Expand Down Expand Up @@ -604,41 +605,41 @@ + (BOOL)requiresMainQueueSetup
}

- (HKHealthStore *)_initializeHealthStore {
if(![self healthStore]) {
self.healthStore = [[HKHealthStore alloc] init];
}
return [self healthStore];
if(![self healthStore]) {
self.healthStore = [[HKHealthStore alloc] init];
}
return [self healthStore];
}


- (void)isHealthKitAvailable:(RCTResponseSenderBlock)callback
{
BOOL isAvailable = NO;

if ([HKHealthStore isHealthDataAvailable]) {
isAvailable = YES;
}

callback(@[[NSNull null], @(isAvailable)]);
}


- (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{
[self _initializeHealthStore];

if ([HKHealthStore isHealthDataAvailable]) {
NSSet *writeDataTypes;
NSSet *readDataTypes;

// get permissions from input object provided by JS options argument
NSDictionary* permissions =[input objectForKey:@"permissions"];
if(permissions != nil){
NSArray* readPermsArray = [permissions objectForKey:@"read"];
NSArray* writePermsArray = [permissions objectForKey:@"write"];
NSSet* readPerms = [self getReadPermsFromOptions:readPermsArray];
NSSet* writePerms = [self getWritePermsFromOptions:writePermsArray];

if(readPerms != nil) {
readDataTypes = readPerms;
}
Expand All @@ -649,17 +650,17 @@ - (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlo
callback(@[RCTMakeError(@"permissions must be provided in the initialization options", nil, nil)]);
return;
}

// make sure at least 1 read or write permission is provided
if(!writeDataTypes && !readDataTypes){
callback(@[RCTMakeError(@"at least 1 read or write permission must be set in options.permissions", nil, nil)]);
return;
}

[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
if (!success) {
NSString *errMsg = [NSString stringWithFormat:@"Error with HealthKit authorization: %@", error];
NSLog(@"%@", errMsg);
NSLog(@"%@", errMsg);
callback(@[RCTMakeError(errMsg, nil, nil)]);
return;
} else {
Expand All @@ -674,6 +675,17 @@ - (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlo
}

- (NSArray<NSString *> *)supportedEvents {
if (!isLegacyBackgroundImplementation) {
return @[
@"healthKit:new",
@"healthKit:failure",
@"healthKit:enabled",
@"healthKit:setup:success",
@"healthKit:setup:failure",
@"change:steps"
];
}

NSArray *types = @[
@"ActiveEnergyBurned",
@"BasalEnergyBurned",
Expand Down Expand Up @@ -704,37 +716,37 @@ - (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlo
NSArray *templates = @[@"healthKit:%@:new", @"healthKit:%@:failure", @"healthKit:%@:enabled", @"healthKit:%@:sample", @"healthKit:%@:setup:success", @"healthKit:%@:setup:failure"];

NSMutableArray *supportedEvents = [[NSMutableArray alloc] init];

for(NSString * type in types) {
for(NSString * template in templates) {
NSString *successEvent = [NSString stringWithFormat:template, type];
[supportedEvents addObject: successEvent];
}
}
[supportedEvents addObject: @"change:steps"];
return supportedEvents;
return supportedEvents;
}

- (void)getModuleInfo:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{
NSDictionary *info = @{
@"name" : @"react-native-apple-healthkit",
@"description" : @"A React Native bridge module for interacting with Apple HealthKit data",
@"className" : @"RCTAppleHealthKit",
@"author": @"Greg Wilson",
@"name" : @"react-native-apple-healthkit",
@"description" : @"A React Native bridge module for interacting with Apple HealthKit data",
@"className" : @"RCTAppleHealthKit",
@"author": @"Greg Wilson",
};
callback(@[[NSNull null], info]);
}

- (void)getAuthorizationStatus:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{

[self _initializeHealthStore];
if ([HKHealthStore isHealthDataAvailable]) {

NSArray* readPermsArray;
NSArray* writePermsArray;

NSDictionary* permissions =[input objectForKey:@"permissions"];
if(permissions != nil && [permissions objectForKey:@"read"] != nil && [permissions objectForKey:@"write"] != nil){
NSArray* readPermsNamesArray = [permissions objectForKey:@"read"];
Expand All @@ -745,8 +757,8 @@ - (void)getAuthorizationStatus:(NSDictionary *)input callback:(RCTResponseSender
callback(@[RCTMakeError(@"permissions must be included in permissions object with read and write options", nil, nil)]);
return;
}


NSMutableArray * read = [NSMutableArray arrayWithCapacity: 1];
for(HKObjectType * perm in readPermsArray) {
[read addObject:[NSNumber numberWithInt:[self.healthStore authorizationStatusForType: perm]]];
Expand All @@ -756,37 +768,55 @@ - (void)getAuthorizationStatus:(NSDictionary *)input callback:(RCTResponseSender
[write addObject:[NSNumber numberWithInt:[self.healthStore authorizationStatusForType: perm]]];
}
callback(@[[NSNull null], @{
@"permissions":
@{
@"read": read,
@"write": write
}
}]);
@"permissions":
@{
@"read": read,
@"write": write
}
}]);
} else {
callback(@[RCTMakeError(@"HealthKit data is not available", nil, nil)]);
}
}

/*!
Initialize background delivery for the specified types. This allows for HealthKit to notify the app when a new
sample of data is added to it
Initialize background delivery for the specified types. This allows for HealthKit to notify the app when a new
sample of data is added to it. This function is to help with backwards compatibility and simulate an optional parameter of isLegacyBackgroundImplementation

This method must be called at the application:didFinishLaunchingWithOptions: method, in AppDelegate.m
*/
- (void)initializeBackgroundObservers:(RCTBridge *)bridge {
[self initializeBackgroundObservers:bridge isLegacyBackgroundImplementation:YES];
}

This method must be called at the application:didFinishLaunchingWithOptions: method, in AppDelegate.m
/*!
Initialize background delivery for the specified types. This allows for HealthKit to notify the app when a new
sample of data is added to it

This method must be called at the application:didFinishLaunchingWithOptions: method, in AppDelegate.m
*/
- (void)initializeBackgroundObservers:(RCTBridge *)bridge
{
- (void)initializeBackgroundObservers:(RCTBridge *)bridge isLegacyBackgroundImplementation:(BOOL)isLegacy {
self.isLegacyBackgroundImplementation = isLegacy;
[self _initializeHealthStore];

self.bridge = bridge;

if ([HKHealthStore isHealthDataAvailable]) {
NSArray *fitnessObservers = @[
@"ActiveEnergyBurned",
@"BasalEnergyBurned",
@"BodyMass",
@"BodyFatPercentage",
@"BodyTemperature",
@"BloodGlucose",
@"BloodPressureDiastolic",
@"BloodPressureSystolic",
@"Cycling",
@"HeartRate",
@"HeartRateVariabilitySDNN",
@"OxygenSaturation",
@"RestingHeartRate",
@"RespiratoryRate",
@"Running",
@"StairClimbing",
@"StepCount",
Expand Down Expand Up @@ -829,30 +859,31 @@ - (void)initializeBackgroundObservers:(RCTBridge *)bridge
// Will be called when this module's first listener is added.
-(void)startObserving {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
for (NSString *notificationName in [self supportedEvents]) {
[center addObserver:self
for (NSString *notificationName in [self supportedEvents]) {
[center addObserver:self
selector:@selector(emitEventInternal:)
name:notificationName
object:nil];
}
}
self.hasListeners = YES;
}

- (void)emitEventInternal:(NSNotification *)notification {
if (self.hasListeners) {
self.callableJSModules = [RCTAppleHealthKit sharedJsModule];
[self.callableJSModules setBridge:self.bridge];
[self sendEventWithName:notification.name
body:notification.userInfo];
}
if (self.hasListeners) {
self.callableJSModules = [RCTAppleHealthKit sharedJsModule];
[self.callableJSModules setBridge:self.bridge];
[self sendEventWithName:notification.name
body:notification.userInfo];
}
}

- (void)emitEventWithName:(NSString *)name andPayload:(NSDictionary *)payload {
- (void)emitEventWithName:(NSString *)name body:(NSDictionary *)body {
[[NSNotificationCenter defaultCenter] postNotificationName:name
object:self
userInfo:payload];
object:self
userInfo:body];
}


// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
self.hasListeners = NO;
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ following statements:
...

/* Add Background initializer for HealthKit */
[[RCTAppleHealthKit new] initializeBackgroundObservers:bridge];
[[RCTAppleHealthKit new] initializeBackgroundObservers:bridge isLegacyBackgroundImplementation:NO];

...

Expand Down
Loading