Skip to content

Add support for Peak Flow rate #135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
- (void)body_getWaistCircumferenceSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)body_saveWaistCircumference:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;

- (void)body_getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)body_getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)body_savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;

- (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)body_getBodyFatPercentageSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
- (void)body_saveBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
Expand Down
77 changes: 77 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,83 @@ - (void)body_saveWaistCircumference:(NSDictionary *)input callback:(RCTResponseS
}];
}

- (void)body_getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];

[self fetchMostRecentQuantitySampleOfType:peakFlowType
predicate:nil
completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) {
if (!mostRecentQuantity) {
NSLog(@"error getting latest peak flow: %@", error);
callback(@[RCTMakeError(@"error getting latest peak flow", error, nil)]);
}
else {
// Determine the peak flow rate in the required unit.
double peakFlow = [mostRecentQuantity doubleValueForUnit:unit];

NSDictionary *response = @{
@"value" : @(peakFlow),
@"startDate" : [RCTAppleHealthKit buildISO8601StringFromDate:startDate],
@"endDate" : [RCTAppleHealthKit buildISO8601StringFromDate:endDate],
};

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

- (void)body_getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];

HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];

NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit];
BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false];
NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil];
NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]];
if(startDate == nil){
callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]);
return;
}
NSPredicate * predicate = [RCTAppleHealthKit predicateForSamplesBetweenDates:startDate endDate:endDate];

[self fetchQuantitySamplesOfType:peakFlowType
unit:unit
predicate:predicate
ascending:ascending
limit:limit
completion:^(NSArray *results, NSError *error) {
if(results){
callback(@[[NSNull null], results]);
return;
} else {
callback(@[RCTJSErrorFromNSError(error)]);
return;
}
}];
}

- (void)body_savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{
double peakFlow = [RCTAppleHealthKit doubleValueFromOptions:input];
NSDate *sampleDate = [RCTAppleHealthKit dateFromOptionsDefaultNow:input];
HKUnit *peakFlowUnit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];

HKQuantity *peakFlowQuantity = [HKQuantity quantityWithUnit:peakFlowUnit doubleValue:peakFlow];
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
HKQuantitySample *peakFlowSample = [HKQuantitySample quantitySampleWithType:peakFlowType quantity:peakFlowQuantity startDate:sampleDate endDate:sampleDate];

[self.healthStore saveObject:peakFlowSample withCompletion:^(BOOL success, NSError *error) {
if (!success) {
callback(@[RCTJSErrorFromNSError(error)]);
return;
}
callback(@[[NSNull null], @(peakFlow)]);
}];
}

- (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
{
Expand Down
4 changes: 4 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ - (nullable HKObjectType *)getReadPermFromText:(nonnull NSString*)key {
return [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex];
} else if ([@"BloodType" isEqualToString: key]) {
return [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBloodType];
} else if ([@"PeakFlow" isEqualToString: key]) {
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
} else if ([@"WaistCircumference" isEqualToString: key] && systemVersion >= 11.0) {
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierWaistCircumference];
}
Expand Down Expand Up @@ -197,6 +199,8 @@ - (nullable HKObjectType *)getWritePermFromText:(nonnull NSString*) key {
// Body Measurements
if ([@"Height" isEqualToString:key]) {
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];
} else if ([@"PeakFlow" isEqualToString: key]) {
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
} else if ([@"WaistCircumference" isEqualToString:key]) {
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierWaistCircumference];
} else if ([@"Weight" isEqualToString:key]) {
Expand Down
3 changes: 3 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit+Utils.m
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ + (HKUnit *)hkUnitFromOptions:(NSDictionary *)options key:(NSString *)key withDe
if ([unitString isEqualToString:@"mmolPerL"]) {
theUnit = [[HKUnit moleUnitWithMetricPrefix:HKMetricPrefixMilli molarMass:HKUnitMolarMassBloodGlucose] unitDividedByUnit:[HKUnit literUnit]];
}
if ([unitString isEqualToString:@"literPerMinute"]) {
theUnit = [[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]];
}
if ([unitString isEqualToString:@"mgPerdL"]) {
theUnit = [HKUnit unitFromString:@"mg/dL"];
}
Expand Down
18 changes: 18 additions & 0 deletions RCTAppleHealthKit/RCTAppleHealthKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ @implementation RCTAppleHealthKit
[self body_saveWaistCircumference:input callback:callback];
}

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

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

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

RCT_EXPORT_METHOD(getLatestBmi:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
{
[self _initializeHealthStore];
Expand Down
25 changes: 25 additions & 0 deletions docs/getLatestPeakFlow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# getLatestPeakFlow

Get the most recent Peak Flow rate value.

On success, the callback function will be provided with a `peakFlow` object containing the Peak Flow Expiratory rate `value`, and the `startDate` and `endDate` of the Peak Flow sample. _Note: startDate and endDate will be the same as samples are saved at a specific point in time._

```javascript
AppleHealthKit.getLatestPeakFlow({}, (err: string, results: HealthValue) => {
if (err) {
console.log('error getting latest Peak Flow rate: ', err)
return
}
console.log(results)
})
```

Example output:

```json
{
"value": 608,
"startDate": "2021-07-21T11:08:05.366+0100",
"endDate": "2021-07-21T11:08:05.366+0100"
}
```
58 changes: 58 additions & 0 deletions docs/getPeakFlowSamples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# getPeakFlowSamples

Query for Peak Flow Expiratory rate samples. The options object is used to setup a query to retrieve relevant samples.

Example input options:

```javascript
let options = {
unit: 'literPerMinute', // optional; default 'literPerMinute'
startDate: new Date(2021, 0, 0).toISOString(), // required
endDate: new Date().toISOString(), // optional; default now
ascending: false, // optional; default false
limit: 10, // optional; default no limit
}
```

Call the method:

```javascript
AppleHealthKit.getPeakFlowSamples(
options,
(err: Object, results: Array<HealthValue>) => {
if (err) {
return
}
console.log(results)
},
)
```

Example output:

Note the first item in the sample was saved from the app using `.savePeakFlow` hence it has the _sourceId_ and _sourceName_ of the app. The others have _Apple Health_ as a _sourceId_ since they were saved in Apple Health directly.

```json
[{
"endDate": "2021-07-21T09:32:24.222+0100",
"id": "16C5F196-AB1D-4F84-9642-7178F05849A3",
"sourceId": "com.mycompany.myapp",
"sourceName": "MyApp",
"startDate": "2021-07-21T09:32:24.222+0100",
"value": 602
}, {
"endDate": "2021-07-19T15:42:00.000+0100",
"id": "803E42FC-2E5B-4C4B-A5DA-4DA74D870F18",
"sourceId": "com.apple.Health",
"sourceName": "Health",
"startDate": "2021-07-19T15:42:00.000+0100",
"value": 608
}, {
"endDate": "2021-07-16T15:42:00.000+0100",
"id": "93421AF8-5CCE-40D5-87D1-3E26404D139B",
"sourceId": "com.apple.Health",
"sourceName": "Health",
"startDate": "2021-07-16T15:42:00.000+0100",
"value": 605
}]
```
35 changes: 35 additions & 0 deletions docs/savePeakFlow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# savePeakFlow

save a numeric Peak Flow rate value to Healthkit.

`savePeakFlow` accepts an options object containing a numeric Peak Expiratory Flow rate value:

Example input options:

```javascript
let options = {
value: 608, // liter per minute
unit: 'literPerMinute' // this is the only accepted unit for Peak Flow rate
}
```

Call the method:

```javascript
AppleHealthKit.savePeakFlow(
(options: HealthValueOptions),
(err: Object, results: number) => {
if (err) {
console.error(err)
return
}
// Peak flow successfully saved
},
)
```

Example output:

```json
608
```
1 change: 1 addition & 0 deletions docs/units.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The following units are supported by the library
hour
inch
joule
literPerMinute
meter
mgPerdL
mile
Expand Down
17 changes: 17 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ declare module 'react-native-health' {
callback: (error: string, result: HealthValue) => void,
): void

getLatestPeakFlow(
options: HealthUnitOptions,
callback: (err: string, results: HealthValue) => void,
): void

getPeakFlowSamples(
options: HealthInputOptions,
callback: (err: string, results: Array<HealthValue>) => void,
): void

savePeakFlow(
options: HealthValueOptions,
callback: (error: string, result: HealthValue) => void,
): void

saveLeanBodyMass(
options: HealthValueOptions,
callback: (error: string, result: HealthValue) => void,
Expand Down Expand Up @@ -568,6 +583,7 @@ declare module 'react-native-health' {
LeanBodyMass = 'LeanBodyMass',
MindfulSession = 'MindfulSession',
NikeFuel = 'NikeFuel',
PeakFlow = 'PeakFlow',
RespiratoryRate = 'RespiratoryRate',
SleepAnalysis = 'SleepAnalysis',
StepCount = 'StepCount',
Expand Down Expand Up @@ -597,6 +613,7 @@ declare module 'react-native-health' {
mile = 'mile',
minute = 'minute',
mmhg = 'mmhg',
literPerMinute = 'literPerMinute',
mmolPerL = 'mmolPerL',
percent = 'percent',
pound = 'pound',
Expand Down
1 change: 1 addition & 0 deletions src/constants/Permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const Permissions = {
LeanBodyMass: 'LeanBodyMass',
MindfulSession: 'MindfulSession',
NikeFuel: 'NikeFuel',
PeakFlow: 'PeakFlow',
RespiratoryRate: 'RespiratoryRate',
SleepAnalysis: 'SleepAnalysis',
StepCount: 'StepCount',
Expand Down