Skip to content

Commit 77554da

Browse files
authored
Merge pull request #135 from kabaros/feature/peak-flow
Add support for Peak Flow rate
2 parents 0b57e9d + ba27710 commit 77554da

13 files changed

+246
-0
lines changed

RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
- (void)body_getWaistCircumferenceSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
2727
- (void)body_saveWaistCircumference:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
2828

29+
- (void)body_getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
30+
- (void)body_getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
31+
- (void)body_savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
32+
2933
- (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
3034
- (void)body_getBodyFatPercentageSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;
3135
- (void)body_saveBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback;

RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m

+75
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,81 @@ - (void)body_saveWaistCircumference:(NSDictionary *)input callback:(RCTResponseS
328328
}];
329329
}
330330

331+
- (void)body_getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
332+
{
333+
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
334+
HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
335+
336+
[self fetchMostRecentQuantitySampleOfType:peakFlowType
337+
predicate:nil
338+
completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) {
339+
if (error) {
340+
NSLog(@"error getting latest peak flow: %@", error);
341+
callback(@[RCTMakeError(@"error getting latest peak flow", error, nil)]);
342+
}
343+
else {
344+
// Determine the peak flow rate in the required unit.
345+
double peakFlow = [mostRecentQuantity doubleValueForUnit:unit];
346+
347+
NSDictionary *response = @{
348+
@"value" : @(peakFlow),
349+
@"startDate" : [RCTAppleHealthKit buildISO8601StringFromDate:startDate],
350+
@"endDate" : [RCTAppleHealthKit buildISO8601StringFromDate:endDate],
351+
};
352+
353+
callback(@[[NSNull null], response]);
354+
}
355+
}];
356+
}
357+
358+
- (void)body_getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
359+
{
360+
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
361+
362+
HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
363+
364+
NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit];
365+
BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false];
366+
NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil];
367+
NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]];
368+
if(startDate == nil){
369+
callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]);
370+
return;
371+
}
372+
NSPredicate * predicate = [RCTAppleHealthKit predicateForSamplesBetweenDates:startDate endDate:endDate];
373+
374+
[self fetchQuantitySamplesOfType:peakFlowType
375+
unit:unit
376+
predicate:predicate
377+
ascending:ascending
378+
limit:limit
379+
completion:^(NSArray *results, NSError *error) {
380+
if(results){
381+
callback(@[[NSNull null], results]);
382+
} else {
383+
callback(@[RCTJSErrorFromNSError(error)]);
384+
}
385+
}];
386+
}
387+
388+
- (void)body_savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
389+
{
390+
double peakFlow = [RCTAppleHealthKit doubleValueFromOptions:input];
391+
NSDate *sampleDate = [RCTAppleHealthKit dateFromOptionsDefaultNow:input];
392+
HKUnit *peakFlowUnit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
393+
394+
HKQuantity *peakFlowQuantity = [HKQuantity quantityWithUnit:peakFlowUnit doubleValue:peakFlow];
395+
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
396+
HKQuantitySample *peakFlowSample = [HKQuantitySample quantitySampleWithType:peakFlowType quantity:peakFlowQuantity startDate:sampleDate endDate:sampleDate];
397+
398+
[self.healthStore saveObject:peakFlowSample withCompletion:^(BOOL success, NSError *error) {
399+
if (!success) {
400+
callback(@[RCTJSErrorFromNSError(error)]);
401+
return;
402+
}
403+
callback(@[[NSNull null], @(peakFlow)]);
404+
}];
405+
}
331406

332407
- (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
333408
{

RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ - (nullable HKObjectType *)getReadPermFromText:(nonnull NSString*)key {
2828
return [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex];
2929
} else if ([@"BloodType" isEqualToString: key]) {
3030
return [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBloodType];
31+
} else if ([@"PeakFlow" isEqualToString: key]) {
32+
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
3133
} else if ([@"WaistCircumference" isEqualToString: key] && systemVersion >= 11.0) {
3234
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierWaistCircumference];
3335
}
@@ -237,6 +239,8 @@ - (nullable HKObjectType *)getWritePermFromText:(nonnull NSString*) key {
237239
// Body Measurements
238240
if ([@"Height" isEqualToString:key]) {
239241
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];
242+
} else if ([@"PeakFlow" isEqualToString: key]) {
243+
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
240244
} else if ([@"WaistCircumference" isEqualToString:key]) {
241245
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierWaistCircumference];
242246
} else if ([@"Weight" isEqualToString:key]) {

RCTAppleHealthKit/RCTAppleHealthKit+Utils.m

+3
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ + (HKUnit *)hkUnitFromOptions:(NSDictionary *)options key:(NSString *)key withDe
293293
if ([unitString isEqualToString:@"mmolPerL"]) {
294294
theUnit = [[HKUnit moleUnitWithMetricPrefix:HKMetricPrefixMilli molarMass:HKUnitMolarMassBloodGlucose] unitDividedByUnit:[HKUnit literUnit]];
295295
}
296+
if ([unitString isEqualToString:@"literPerMinute"]) {
297+
theUnit = [[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]];
298+
}
296299
if ([unitString isEqualToString:@"mgPerdL"]) {
297300
theUnit = [HKUnit unitFromString:@"mg/dL"];
298301
}

RCTAppleHealthKit/RCTAppleHealthKit.m

+18
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,24 @@ + (BOOL)requiresMainQueueSetup
151151
[self body_saveWaistCircumference:input callback:callback];
152152
}
153153

154+
RCT_EXPORT_METHOD(getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
155+
{
156+
[self _initializeHealthStore];
157+
[self body_getLatestPeakFlow:input callback:callback];
158+
}
159+
160+
RCT_EXPORT_METHOD(getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
161+
{
162+
[self _initializeHealthStore];
163+
[self body_getPeakFlowSamples:input callback:callback];
164+
}
165+
166+
RCT_EXPORT_METHOD(savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
167+
{
168+
[self _initializeHealthStore];
169+
[self body_savePeakFlow:input callback:callback];
170+
}
171+
154172
RCT_EXPORT_METHOD(getLatestBmi:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
155173
{
156174
[self _initializeHealthStore];

docs/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ There is a gitbook version for the documentation on this [link](https://vinicius
117117
- [getOxygenSaturationSamples](getOxygenSaturationSamples.md)
118118
- [getWalkingHeartRateAverage](getWalkingHeartRateAverage.md)
119119
- [saveBmi](saveBmi.md)
120+
- [getPeakFlowSamples](getPeakFlowSamples.md)
121+
- [getLatestPeakFlow](getLatestPeakFlow.md)
122+
- [savePeakFlow](savePeakFlow.md)
120123

121124
#### Workout Methods
122125

docs/getLatestPeakFlow.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# getLatestPeakFlow
2+
3+
Get the most recent Peak Flow rate value.
4+
5+
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._
6+
7+
```javascript
8+
AppleHealthKit.getLatestPeakFlow({}, (err: string, results: HealthValue) => {
9+
if (err) {
10+
console.log('error getting latest Peak Flow rate: ', err)
11+
return
12+
}
13+
console.log(results)
14+
})
15+
```
16+
17+
Example output:
18+
19+
```json
20+
{
21+
"value": 608,
22+
"startDate": "2021-07-21T11:08:05.366+0100",
23+
"endDate": "2021-07-21T11:08:05.366+0100"
24+
}
25+
```

docs/getPeakFlowSamples.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# getPeakFlowSamples
2+
3+
Query for Peak Flow Expiratory rate samples. The options object is used to setup a query to retrieve relevant samples.
4+
5+
Example input options:
6+
7+
```javascript
8+
let options = {
9+
unit: 'literPerMinute', // optional; default 'literPerMinute'
10+
startDate: new Date(2021, 0, 0).toISOString(), // required
11+
endDate: new Date().toISOString(), // optional; default now
12+
ascending: false, // optional; default false
13+
limit: 10, // optional; default no limit
14+
}
15+
```
16+
17+
Call the method:
18+
19+
```javascript
20+
AppleHealthKit.getPeakFlowSamples(
21+
options,
22+
(err: Object, results: Array<HealthValue>) => {
23+
if (err) {
24+
return
25+
}
26+
console.log(results)
27+
},
28+
)
29+
```
30+
31+
Example output:
32+
33+
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.
34+
35+
```json
36+
[{
37+
"endDate": "2021-07-21T09:32:24.222+0100",
38+
"id": "16C5F196-AB1D-4F84-9642-7178F05849A3",
39+
"sourceId": "com.mycompany.myapp",
40+
"sourceName": "MyApp",
41+
"startDate": "2021-07-21T09:32:24.222+0100",
42+
"value": 602
43+
}, {
44+
"endDate": "2021-07-19T15:42:00.000+0100",
45+
"id": "803E42FC-2E5B-4C4B-A5DA-4DA74D870F18",
46+
"sourceId": "com.apple.Health",
47+
"sourceName": "Health",
48+
"startDate": "2021-07-19T15:42:00.000+0100",
49+
"value": 608
50+
}, {
51+
"endDate": "2021-07-16T15:42:00.000+0100",
52+
"id": "93421AF8-5CCE-40D5-87D1-3E26404D139B",
53+
"sourceId": "com.apple.Health",
54+
"sourceName": "Health",
55+
"startDate": "2021-07-16T15:42:00.000+0100",
56+
"value": 605
57+
}]
58+
```

docs/permissions.md

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ AppleHealthKit.initHealthKit(permissions, (error: string) => {
110110
WalkingHeartRateAverage
111111
Weight
112112
Workout
113+
PeakFlow
113114
```
114115
115116
## Supported Apple Permissions
@@ -157,3 +158,4 @@ The available Healthkit identifiers are supported
157158
| Weight | [HKQuantityTypeIdentifierBodyMass](https://developer.apple.com/reference/Healthkit/hkquantitytypeidentifierbodymass?language=objc) | ✓ | ✓ |
158159
| BodyFatPercentage | [HKQuantityTypeIdentifierBodyFatPercentage](https://developer.apple.com/reference/Healthkit/hkquantitytypeidentifierbodyfatpercentage?language=objc) | ✓ | ✓ |
159160
| WalkingHeartRateAverage | [HKQuantityTypeIdentifierWalkingHeartRateAverage](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifierwalkingheartrateaverage?language=objc) | ✓ | |
161+
| PeakFlow | [HKQuantityTypeIdentifierPeakExpiratoryFlowRate](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifierpeakexpiratoryflowrate?=objc) | ✓ | ✓ |

docs/savePeakFlow.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# savePeakFlow
2+
3+
save a numeric Peak Flow rate value to Healthkit.
4+
5+
`savePeakFlow` accepts an options object containing a numeric Peak Expiratory Flow rate value:
6+
7+
Example input options:
8+
9+
```javascript
10+
let options = {
11+
value: 608, // liter per minute
12+
unit: 'literPerMinute' // this is the only accepted unit for Peak Flow rate
13+
}
14+
```
15+
16+
Call the method:
17+
18+
```javascript
19+
AppleHealthKit.savePeakFlow(
20+
(options: HealthValueOptions),
21+
(err: Object, results: number) => {
22+
if (err) {
23+
console.error(err)
24+
return
25+
}
26+
// Peak flow successfully saved
27+
},
28+
)
29+
```
30+
31+
Example output:
32+
33+
```json
34+
608
35+
```

docs/units.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The following units are supported by the library
1414
hour
1515
inch
1616
joule
17+
literPerMinute
1718
meter
1819
mgPerdL
1920
mile

index.d.ts

+17
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ declare module 'react-native-health' {
8787
callback: (error: string, result: HealthValue) => void,
8888
): void
8989

90+
getLatestPeakFlow(
91+
options: HealthUnitOptions,
92+
callback: (err: string, results: HealthValue) => void,
93+
): void
94+
95+
getPeakFlowSamples(
96+
options: HealthInputOptions,
97+
callback: (err: string, results: Array<HealthValue>) => void,
98+
): void
99+
100+
savePeakFlow(
101+
options: HealthValueOptions,
102+
callback: (error: string, result: HealthValue) => void,
103+
): void
104+
90105
saveLeanBodyMass(
91106
options: HealthValueOptions,
92107
callback: (error: string, result: HealthValue) => void,
@@ -742,6 +757,7 @@ declare module 'react-native-health' {
742757
LeanBodyMass = 'LeanBodyMass',
743758
MindfulSession = 'MindfulSession',
744759
NikeFuel = 'NikeFuel',
760+
PeakFlow = 'PeakFlow',
745761
RespiratoryRate = 'RespiratoryRate',
746762
SleepAnalysis = 'SleepAnalysis',
747763
StepCount = 'StepCount',
@@ -772,6 +788,7 @@ declare module 'react-native-health' {
772788
mile = 'mile',
773789
minute = 'minute',
774790
mmhg = 'mmhg',
791+
literPerMinute = 'literPerMinute',
775792
mmolPerL = 'mmolPerL',
776793
percent = 'percent',
777794
pound = 'pound',

src/constants/Permissions.js

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export const Permissions = {
8282
LeanBodyMass: 'LeanBodyMass',
8383
MindfulSession: 'MindfulSession',
8484
NikeFuel: 'NikeFuel',
85+
PeakFlow: 'PeakFlow',
8586
RespiratoryRate: 'RespiratoryRate',
8687
SleepAnalysis: 'SleepAnalysis',
8788
StepCount: 'StepCount',

0 commit comments

Comments
 (0)