Skip to content

Commit d2e1bf0

Browse files
author
Mozafar Haider
committed
Add support for Peak Flow Expiratory Rate
1 parent 193dc2b commit d2e1bf0

11 files changed

+243
-0
lines changed

RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h

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

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

RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m

+77
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,83 @@ - (void)body_saveWaistCircumference:(NSDictionary *)input callback:(RCTResponseS
297297
}];
298298
}
299299

300+
- (void)body_getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
301+
{
302+
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
303+
HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
304+
305+
[self fetchMostRecentQuantitySampleOfType:peakFlowType
306+
predicate:nil
307+
completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) {
308+
if (!mostRecentQuantity) {
309+
NSLog(@"error getting latest peak flow: %@", error);
310+
callback(@[RCTMakeError(@"error getting latest peak flow", error, nil)]);
311+
}
312+
else {
313+
// Determine the peak flow rate in the required unit.
314+
double peakFlow = [mostRecentQuantity doubleValueForUnit:unit];
315+
316+
NSDictionary *response = @{
317+
@"value" : @(peakFlow),
318+
@"startDate" : [RCTAppleHealthKit buildISO8601StringFromDate:startDate],
319+
@"endDate" : [RCTAppleHealthKit buildISO8601StringFromDate:endDate],
320+
};
321+
322+
callback(@[[NSNull null], response]);
323+
}
324+
}];
325+
}
326+
327+
- (void)body_getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
328+
{
329+
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
330+
331+
HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
332+
333+
NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit];
334+
BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false];
335+
NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil];
336+
NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]];
337+
if(startDate == nil){
338+
callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]);
339+
return;
340+
}
341+
NSPredicate * predicate = [RCTAppleHealthKit predicateForSamplesBetweenDates:startDate endDate:endDate];
342+
343+
[self fetchQuantitySamplesOfType:peakFlowType
344+
unit:unit
345+
predicate:predicate
346+
ascending:ascending
347+
limit:limit
348+
completion:^(NSArray *results, NSError *error) {
349+
if(results){
350+
callback(@[[NSNull null], results]);
351+
return;
352+
} else {
353+
callback(@[RCTJSErrorFromNSError(error)]);
354+
return;
355+
}
356+
}];
357+
}
358+
359+
- (void)body_savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
360+
{
361+
double peakFlow = [RCTAppleHealthKit doubleValueFromOptions:input];
362+
NSDate *sampleDate = [RCTAppleHealthKit dateFromOptionsDefaultNow:input];
363+
HKUnit *peakFlowUnit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
364+
365+
HKQuantity *peakFlowQuantity = [HKQuantity quantityWithUnit:peakFlowUnit doubleValue:peakFlow];
366+
HKQuantityType *peakFlowType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
367+
HKQuantitySample *peakFlowSample = [HKQuantitySample quantitySampleWithType:peakFlowType quantity:peakFlowQuantity startDate:sampleDate endDate:sampleDate];
368+
369+
[self.healthStore saveObject:peakFlowSample withCompletion:^(BOOL success, NSError *error) {
370+
if (!success) {
371+
callback(@[RCTJSErrorFromNSError(error)]);
372+
return;
373+
}
374+
callback(@[[NSNull null], @(peakFlow)]);
375+
}];
376+
}
300377

301378
- (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback
302379
{

RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ - (nullable HKObjectType *)getReadPermFromText:(nonnull NSString*)key {
2727
return [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex];
2828
} else if ([@"BloodType" isEqualToString: key]) {
2929
return [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBloodType];
30+
} else if ([@"PeakFlow" isEqualToString: key]) {
31+
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
3032
} else if ([@"WaistCircumference" isEqualToString: key] && systemVersion >= 11.0) {
3133
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierWaistCircumference];
3234
}
@@ -197,6 +199,8 @@ - (nullable HKObjectType *)getWritePermFromText:(nonnull NSString*) key {
197199
// Body Measurements
198200
if ([@"Height" isEqualToString:key]) {
199201
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];
202+
} else if ([@"PeakFlow" isEqualToString: key]) {
203+
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierPeakExpiratoryFlowRate];
200204
} else if ([@"WaistCircumference" isEqualToString:key]) {
201205
return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierWaistCircumference];
202206
} else if ([@"Weight" isEqualToString:key]) {

RCTAppleHealthKit/RCTAppleHealthKit+Utils.m

+3
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ + (HKUnit *)hkUnitFromOptions:(NSDictionary *)options key:(NSString *)key withDe
263263
if ([unitString isEqualToString:@"mmolPerL"]) {
264264
theUnit = [[HKUnit moleUnitWithMetricPrefix:HKMetricPrefixMilli molarMass:HKUnitMolarMassBloodGlucose] unitDividedByUnit:[HKUnit literUnit]];
265265
}
266+
if ([unitString isEqualToString:@"literPerMinute"]) {
267+
theUnit = [[HKUnit literUnit] unitDividedByUnit:[HKUnit minuteUnit]];
268+
}
266269
if ([unitString isEqualToString:@"mgPerdL"]) {
267270
theUnit = [HKUnit unitFromString:@"mg/dL"];
268271
}

RCTAppleHealthKit/RCTAppleHealthKit.m

+18
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@ @implementation RCTAppleHealthKit
118118
[self body_saveWaistCircumference:input callback:callback];
119119
}
120120

121+
RCT_EXPORT_METHOD(getLatestPeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
122+
{
123+
[self _initializeHealthStore];
124+
[self body_getLatestPeakFlow:input callback:callback];
125+
}
126+
127+
RCT_EXPORT_METHOD(getPeakFlowSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
128+
{
129+
[self _initializeHealthStore];
130+
[self body_getPeakFlowSamples:input callback:callback];
131+
}
132+
133+
RCT_EXPORT_METHOD(savePeakFlow:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
134+
{
135+
[self _initializeHealthStore];
136+
[self body_savePeakFlow:input callback:callback];
137+
}
138+
121139
RCT_EXPORT_METHOD(getLatestBmi:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback)
122140
{
123141
[self _initializeHealthStore];

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/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
@@ -83,6 +83,21 @@ declare module 'react-native-health' {
8383
callback: (error: string, result: HealthValue) => void,
8484
): void
8585

86+
getLatestPeakFlow(
87+
options: HealthUnitOptions,
88+
callback: (err: string, results: HealthValue) => void,
89+
): void
90+
91+
getPeakFlowSamples(
92+
options: HealthInputOptions,
93+
callback: (err: string, results: Array<HealthValue>) => void,
94+
): void
95+
96+
savePeakFlow(
97+
options: HealthValueOptions,
98+
callback: (error: string, result: HealthValue) => void,
99+
): void
100+
86101
saveLeanBodyMass(
87102
options: HealthValueOptions,
88103
callback: (error: string, result: HealthValue) => void,
@@ -568,6 +583,7 @@ declare module 'react-native-health' {
568583
LeanBodyMass = 'LeanBodyMass',
569584
MindfulSession = 'MindfulSession',
570585
NikeFuel = 'NikeFuel',
586+
PeakFlow = 'PeakFlow',
571587
RespiratoryRate = 'RespiratoryRate',
572588
SleepAnalysis = 'SleepAnalysis',
573589
StepCount = 'StepCount',
@@ -597,6 +613,7 @@ declare module 'react-native-health' {
597613
mile = 'mile',
598614
minute = 'minute',
599615
mmhg = 'mmhg',
616+
literPerMinute = 'literPerMinute',
600617
mmolPerL = 'mmolPerL',
601618
percent = 'percent',
602619
pound = 'pound',

src/constants/Permissions.js

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const Permissions = {
6969
LeanBodyMass: 'LeanBodyMass',
7070
MindfulSession: 'MindfulSession',
7171
NikeFuel: 'NikeFuel',
72+
PeakFlow: 'PeakFlow',
7273
RespiratoryRate: 'RespiratoryRate',
7374
SleepAnalysis: 'SleepAnalysis',
7475
StepCount: 'StepCount',

0 commit comments

Comments
 (0)