@kingstinct/react-native-healthkit 9.0.0 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/ios/Helpers.swift +33 -37
- package/ios/WorkoutProxy.swift +7 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @kingstinct/react-native-healthkit
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Kingstinct/react-native-healthkit/actions/workflows/test.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@kingstinct/react-native-healthkit)
|
|
5
|
+
[](https://www.npmjs.com/package/@kingstinct/react-native-healthkit)
|
|
6
|
+
[](https://discord.gg/hrgnETpsJA)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
React Native bindings for HealthKit with full TypeScript and Promise support covering about any kind of data. Keeping TypeScript mappings as close as possible to HealthKit - both in regards to naming and serialization. This will make it easier to keep this library up-to-date with HealthKit as well as browsing [the official documentation](https://developer.apple.com/documentation/healthkit) (and if something - metadata properties for example - is not typed it will still be accessible).
|
|
10
|
+
|
|
11
|
+
| Data Types | Query | Save | Subscribe | Examples |
|
|
12
|
+
| ----------------------------|:------|:------|:----------|:---------------------------------------|
|
|
13
|
+
| 100+ Quantity Types | ✅ | ✅ | ✅ | Steps, energy burnt, blood glucose etc.. |
|
|
14
|
+
| 63 Category Types | ✅ | ✅ | ✅ | Sleep analysis, mindful sessions etc.. |
|
|
15
|
+
| 75+ Workout Activity Types | ✅ | ✅ | ✅ | Swimming, running, table tennis etc.. |
|
|
16
|
+
| Correlation Types | ✅ | ✅ | ✅ | Food and blood pressure |
|
|
17
|
+
| Document Types | ✅ | ❌ | ✅ | [CDA documents](https://developer.apple.com/documentation/healthkit/hkcdadocument) exposed as Base64 data |
|
|
18
|
+
| Clinical Records | ⚠️ | ❌ | ⚠️ | Lab results etc in [FHIR JSON format](https://www.hl7.org/fhir/json.html) (see [Clinical Records](https://github.com/kingstinct/react-native-healthkit#clinical-records)) |
|
|
19
|
+
|
|
20
|
+
### Disclaimer
|
|
21
|
+
|
|
22
|
+
This library is provided as-is without any warranty and is not affiliated with Apple in any way. The data might be incomplete or inaccurate.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Expo
|
|
27
|
+
Usage with Expo is possible - just keep in mind it will not work in Expo Go and [you'll need to roll your own Dev Client](https://docs.expo.dev/development/getting-started/).
|
|
28
|
+
|
|
29
|
+
1. `yarn add @kingstinct/react-native-healthkit`
|
|
30
|
+
2. Update your app.json with the config plugin:
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"expo": {
|
|
34
|
+
"plugins": ["@kingstinct/react-native-healthkit"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
this will give you defaults that make the app build without any further configuration. If you want, you can override the defaults:
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"expo": {
|
|
42
|
+
"plugins": [
|
|
43
|
+
["@kingstinct/react-native-healthkit", {
|
|
44
|
+
"NSHealthShareUsageDescription": "Your own custom usage description",
|
|
45
|
+
"NSHealthUpdateUsageDescription": false, // if you have no plans to update data, you could skip adding it to your info.plist
|
|
46
|
+
"background": false // if you have no plans to use it in background mode, you could skip adding it to the entitlements
|
|
47
|
+
}]
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
3. Build a new Dev Client
|
|
53
|
+
|
|
54
|
+
### Native or Expo Bare Workflow
|
|
55
|
+
1. `yarn add @kingstinct/react-native-healthkit`
|
|
56
|
+
2. `npx pod-install`
|
|
57
|
+
3. Set `NSHealthUpdateUsageDescription` and `NSHealthShareUsageDescription` in your `Info.plist`
|
|
58
|
+
4. Enable the HealthKit capability for the project in Xcode.
|
|
59
|
+
5. Since this package is using Swift you might also need to add a bridging header in your project if you haven't already, you can [find more about that in the official React Native docs](https://reactnative.dev/docs/native-modules-ios#exporting-swift)
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
During runtime check and request permissions with `requestAuthorization`. Failing to request authorization, or requesting a permission you haven't requested yet, will result in the app crashing. This is easy to miss - for example by requesting authorization in the same component where you have a hook trying to fetch data right away.. :)
|
|
64
|
+
|
|
65
|
+
Some hook examples:
|
|
66
|
+
```TypeScript
|
|
67
|
+
import { useHealthkitAuthorization, saveQuantitySample } from '@kingstinct/react-native-healthkit';
|
|
68
|
+
|
|
69
|
+
const [authorizationStatus, requestAuthorization] = useHealthkitAuthorization(['HKQuantityTypeIdentifierBloodGlucose'])
|
|
70
|
+
|
|
71
|
+
// make sure that you've requested authorization before requesting data, otherwise your app will crash
|
|
72
|
+
import { useMostRecentQuantitySample, HKQuantityTypeIdentifier, useMostRecentCategorySample } from '@kingstinct/react-native-healthkit';
|
|
73
|
+
|
|
74
|
+
const mostRecentBloodGlucoseSample = useMostRecentQuantitySample('HKQuantityTypeIdentifierBloodGlucose')
|
|
75
|
+
const lastBodyFatSample = useMostRecentQuantitySample('HKQuantityTypeIdentifierBodyFatPercentage')
|
|
76
|
+
const lastMindfulSession = useMostRecentCategorySample('HKCategoryTypeIdentifierMindfulSession')
|
|
77
|
+
const lastWorkout = useMostRecentWorkout()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Some imperative examples:
|
|
81
|
+
```TypeScript
|
|
82
|
+
import { isHealthDataAvailable, requestAuthorization, subscribeToChanges, saveQuantitySample, getMostRecentQuantitySample } from '@kingstinct/react-native-healthkit';
|
|
83
|
+
|
|
84
|
+
const isAvailable = await isHealthDataAvailable();
|
|
85
|
+
|
|
86
|
+
/* Read latest sample of any data */
|
|
87
|
+
await requestAuthorization(['HKQuantityTypeIdentifierBodyFatPercentage']); // request read permission for bodyFatPercentage
|
|
88
|
+
|
|
89
|
+
const { quantity, unit, startDate, endDate } = await getMostRecentQuantitySample('HKQuantityTypeIdentifierBodyFatPercentage'); // read latest sample
|
|
90
|
+
|
|
91
|
+
console.log(quantity) // 17.5
|
|
92
|
+
console.log(unit) // %
|
|
93
|
+
|
|
94
|
+
await requestAuthorization(['HKQuantityTypeIdentifierHeartRate']); // request read permission for heart rate
|
|
95
|
+
|
|
96
|
+
/* Subscribe to data (Make sure to request permissions before subscribing to changes) */
|
|
97
|
+
const [hasRequestedAuthorization, setHasRequestedAuthorization] = useState(false);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
requestAuthorization(['HKQuantityTypeIdentifierHeartRate']).then(() => {
|
|
101
|
+
setHasRequestedAuthorization(true);
|
|
102
|
+
});
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (hasRequestedAuthorization) {
|
|
107
|
+
const unsubscribe = subscribeToChanges(HKQuantityTypeIdentifier.heartRate, () => {
|
|
108
|
+
// refetch data as needed
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return () => unsubscribe();
|
|
112
|
+
}
|
|
113
|
+
}, [hasRequestedAuthorization]);
|
|
114
|
+
|
|
115
|
+
/* write data */
|
|
116
|
+
await requestAuthorization([], [HKQuantityTypeIdentifier.insulinDelivery]); // request write permission for insulin delivery
|
|
117
|
+
|
|
118
|
+
saveQuantitySample(
|
|
119
|
+
'HKQuantityTypeIdentifierInsulinDelivery',
|
|
120
|
+
'IU',
|
|
121
|
+
5.5,
|
|
122
|
+
{
|
|
123
|
+
metadata: {
|
|
124
|
+
// Metadata keys could be arbirtary string to store app-specific data.
|
|
125
|
+
// To use built-in types from https://developer.apple.com/documentation/healthkit/samples/metadata_keys
|
|
126
|
+
// you need to specify string values instead of variable names (by dropping MetadataKey from the name).
|
|
127
|
+
HKInsulinDeliveryReason: HKInsulinDeliveryReason.basal,
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### HealthKit Anchors
|
|
134
|
+
In 6.0 you can use HealthKit anchors to get changes and deleted items which is very useful for syncing. This is a breaking change - but a very easy one to handle that TypeScript should help you with. Most queries now return an object containing samples which is what was returned as only an array before.
|
|
135
|
+
|
|
136
|
+
```newAnchor``` is a base64-encoded string returned from HealthKit that contain sync information. After each successful sync, store the anchor for the next time your anchor query is called to only return the values that have changed.
|
|
137
|
+
|
|
138
|
+
```limit``` will indicate how many records to consider when sycning data, you can set this value to 0 indicate no limit.
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
|
|
142
|
+
```TypeScript
|
|
143
|
+
const { newAnchor, samples, deletedSamples } = await queryQuantitySamplesWithAnchor('HKQuantityTypeIdentifierStepCount', {
|
|
144
|
+
limit: 2,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const nextResult = await queryQuantitySamplesWithAnchor('HKQuantityTypeIdentifierStepCount', {
|
|
148
|
+
limit: 2,
|
|
149
|
+
anchor: newAnchor,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// etc..
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Migration to 9.0.0
|
|
156
|
+
|
|
157
|
+
There are a lot of under-the-hood changes in version 9.0.0, some of them are breaking (although I've tried to reduce it as much as possible).
|
|
158
|
+
- Most of all - the library has been migrated to use react-native-nitro-modules. This improves performance, type-safety and gets rid of a lot of boilerplate code that made it harder to maintain and add features to the library.
|
|
159
|
+
- Naming conventions have changed - most of the HK-prefixed stuff has been removed to avoid conflicts on the native side and also make the library more beautiful to look at. As an example the type previously called HKQuantityTypeIdentifier is not just QuantityTypeIdentifier on the library level.
|
|
160
|
+
- Less required params by default - making it easier to start using the library, for example calling `queryQuantitySamples('HKQuantityTypeIdentifierStepCount')` will simply return the last 20 samples.
|
|
161
|
+
- Flexible filters that map closer to the native constructs. This can easily be extended.
|
|
162
|
+
- `deleteObjects` replaces all previous deletion methods, using the new flexible filters.
|
|
163
|
+
- Workouts are returned as proxies containing not only data but also functions, for example `getWorkoutRoutes`.
|
|
164
|
+
- Identifiers are now just strings, and more strictly typed.
|
|
165
|
+
- Units are now just strings.
|
|
166
|
+
|
|
167
|
+
## A note on Apple Documentation
|
|
168
|
+
|
|
169
|
+
We're striving to do as straight a mapping as possible to the Native Libraries. This means that in most cases the Apple Documentation makes sense. However, when it comes to the Healthkit [Metadata Keys](https://developer.apple.com/documentation/healthkit/samples/metadata_keys) the documentation doesn't actually reflect the serialized values. For example HKMetadataKeyExternalUUID in the documentation serializes to HKExternalUUID - which is what we use.
|
|
170
|
+
|
|
171
|
+
## Clinical Records
|
|
172
|
+
|
|
173
|
+
For accessing Clinical Records use old version (3.x) or use specific branch "including-clinical-records". The reason is we cannot refer to this code natively in apps without getting approval from Apple, this could probably be solved by the config plugin but we haven't had time to look into it yet.
|
|
174
|
+
|
|
175
|
+
## Android alternatives
|
|
176
|
+
|
|
177
|
+
For a similar library for Android, check out [react-native-health-connect](https://github.com/matinzd/react-native-health-connect/) that works with the new Health Connect. For Google Fit [react-native-google-fit](https://www.npmjs.com/package/react-native-google-fit) seems to be the most popular option, and and another possible option is to work directly with the Google Fit REST API which I've some experience with.
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
182
|
+
|
|
183
|
+
## Sponsorship and enterprise-grade support
|
|
184
|
+
|
|
185
|
+
If you're using @kingstinct/react-native-healthkit to build your production app [please consider funding its continued development](https://github.com/sponsors/Kingstinct). It helps us spend more time on keeping this library as good as it can be.
|
|
186
|
+
|
|
187
|
+
At Kingstinct we're also able to provide enterprise-grade support for this package, [find us here](https://kingstinct.com) or [drop an email](mailto:healthkit@kingstinct.com) for more information. Also feel free to join our [Discord community](https://discord.gg/EHScS93v).
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
192
|
+
|
package/ios/Helpers.swift
CHANGED
|
@@ -11,25 +11,24 @@ import NitroModules
|
|
|
11
11
|
|
|
12
12
|
func dateOrNilIfZero(_ timestamp: Double?) -> Date? {
|
|
13
13
|
if let timestamp = timestamp {
|
|
14
|
-
if
|
|
14
|
+
if timestamp == 0 {
|
|
15
15
|
return nil
|
|
16
16
|
}
|
|
17
17
|
return Date.init(timeIntervalSince1970: timestamp)
|
|
18
18
|
}
|
|
19
19
|
return nil
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
func getQueryLimit(_ limit: Double?) -> Int {
|
|
25
24
|
if let limit = limit {
|
|
26
|
-
if
|
|
25
|
+
if limit == .infinity || limit <= 0 {
|
|
27
26
|
return HKObjectQueryNoLimit
|
|
28
27
|
}
|
|
29
|
-
|
|
28
|
+
|
|
30
29
|
return Int(limit)
|
|
31
30
|
}
|
|
32
|
-
|
|
31
|
+
|
|
33
32
|
return DEFAULT_QUERY_LIMIT
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -45,7 +44,7 @@ func createPredicateForWorkout(filter: PredicateForWorkouts) throws -> NSPredica
|
|
|
45
44
|
return createDatePredicate(dateFilter: dateFilter)
|
|
46
45
|
case .fifth(let w):
|
|
47
46
|
if let w = w.workout as? WorkoutProxy {
|
|
48
|
-
return
|
|
47
|
+
return w.workoutPredicate
|
|
49
48
|
}
|
|
50
49
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
51
50
|
case .sixth(let activityType):
|
|
@@ -55,7 +54,7 @@ func createPredicateForWorkout(filter: PredicateForWorkouts) throws -> NSPredica
|
|
|
55
54
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized workoutActivityType with identifier \(activityType.workoutActivityType.rawValue)")
|
|
56
55
|
}
|
|
57
56
|
case .seventh(let durationPredicate):
|
|
58
|
-
if let op = NSComparisonPredicate.Operator(rawValue: UInt(durationPredicate.predicateOperator.rawValue)){
|
|
57
|
+
if let op = NSComparisonPredicate.Operator(rawValue: UInt(durationPredicate.predicateOperator.rawValue)) {
|
|
59
58
|
return HKQuery.predicateForWorkouts(with: op, duration: durationPredicate.durationInSeconds)
|
|
60
59
|
}
|
|
61
60
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized workout duration predicate operator \(durationPredicate.predicateOperator.rawValue)")
|
|
@@ -75,7 +74,7 @@ func createPredicateForWorkout(filter: Variant_PredicateWithUUID_PredicateWithUU
|
|
|
75
74
|
return createDatePredicate(dateFilter: dateFilter)
|
|
76
75
|
case .fifth(let w):
|
|
77
76
|
if let w = w.workout as? WorkoutProxy {
|
|
78
|
-
return
|
|
77
|
+
return w.workoutPredicate
|
|
79
78
|
}
|
|
80
79
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
81
80
|
case .sixth(let activityType):
|
|
@@ -85,11 +84,11 @@ func createPredicateForWorkout(filter: Variant_PredicateWithUUID_PredicateWithUU
|
|
|
85
84
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized workoutActivityType with identifier \(activityType.workoutActivityType.rawValue)")
|
|
86
85
|
}
|
|
87
86
|
case .seventh(let durationPredicate):
|
|
88
|
-
if let op = NSComparisonPredicate.Operator(rawValue: UInt(durationPredicate.predicateOperator.rawValue)){
|
|
87
|
+
if let op = NSComparisonPredicate.Operator(rawValue: UInt(durationPredicate.predicateOperator.rawValue)) {
|
|
89
88
|
return HKQuery.predicateForWorkouts(with: op, duration: durationPredicate.durationInSeconds)
|
|
90
89
|
}
|
|
91
90
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized workout duration predicate operator \(durationPredicate.predicateOperator.rawValue)")
|
|
92
|
-
|
|
91
|
+
|
|
93
92
|
case .eigth(let or):
|
|
94
93
|
return NSCompoundPredicate.init(andPredicateWithSubpredicates: try or.OR.map { predicate in
|
|
95
94
|
return try createPredicateForWorkout(filter: predicate)
|
|
@@ -101,14 +100,14 @@ func createPredicateForWorkout(filter: Variant_PredicateWithUUID_PredicateWithUU
|
|
|
101
100
|
})
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
|
-
|
|
103
|
+
|
|
105
104
|
return nil
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
func createDatePredicate(dateFilter: PredicateWithStartAndEnd) -> NSPredicate {
|
|
109
108
|
let strictStartDate = dateFilter.strictStartDate ?? false
|
|
110
109
|
let strictEndDate = dateFilter.strictEndDate ?? false
|
|
111
|
-
|
|
110
|
+
|
|
112
111
|
let options: HKQueryOptions = strictStartDate && strictEndDate
|
|
113
112
|
? [.strictStartDate, .strictEndDate]
|
|
114
113
|
: strictEndDate
|
|
@@ -116,7 +115,7 @@ func createDatePredicate(dateFilter: PredicateWithStartAndEnd) -> NSPredicate {
|
|
|
116
115
|
: strictStartDate
|
|
117
116
|
? .strictStartDate
|
|
118
117
|
: []
|
|
119
|
-
|
|
118
|
+
|
|
120
119
|
return HKQuery.predicateForSamples(
|
|
121
120
|
withStart: dateFilter.startDate,
|
|
122
121
|
end: dateFilter.endDate,
|
|
@@ -129,8 +128,7 @@ func createUUIDsPredicate(uuidsWrapper: PredicateWithUUIDs) -> NSPredicate {
|
|
|
129
128
|
do {
|
|
130
129
|
let uuid = try initializeUUID(uuidStr)
|
|
131
130
|
return uuid
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
131
|
+
} catch {
|
|
134
132
|
print(error.localizedDescription)
|
|
135
133
|
return nil
|
|
136
134
|
}
|
|
@@ -151,7 +149,7 @@ func createPredicate(filter: Variant_PredicateWithUUID_PredicateWithUUIDs_Predic
|
|
|
151
149
|
return createDatePredicate(dateFilter: dateFilter)
|
|
152
150
|
case .fifth(let w):
|
|
153
151
|
if let w = w.workout as? WorkoutProxy {
|
|
154
|
-
return
|
|
152
|
+
return w.workoutPredicate
|
|
155
153
|
}
|
|
156
154
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
157
155
|
}
|
|
@@ -172,7 +170,7 @@ func createPredicate(filter: Variant_PredicateWithUUID_PredicateWithUUIDs_Predic
|
|
|
172
170
|
return createDatePredicate(dateFilter: dateFilter)
|
|
173
171
|
case .fifth(let w):
|
|
174
172
|
if let w = w.workout as? WorkoutProxy {
|
|
175
|
-
return
|
|
173
|
+
return w.workoutPredicate
|
|
176
174
|
}
|
|
177
175
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
178
176
|
case .sixth(let and):
|
|
@@ -201,7 +199,7 @@ func createPredicateForSamples(filter: PredicateForSamples) throws -> NSPredicat
|
|
|
201
199
|
return createDatePredicate(dateFilter: dateFilter)
|
|
202
200
|
case .fifth(let w):
|
|
203
201
|
if let w = w.workout as? WorkoutProxy {
|
|
204
|
-
return
|
|
202
|
+
return w.workoutPredicate
|
|
205
203
|
}
|
|
206
204
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
207
205
|
}
|
|
@@ -219,7 +217,7 @@ func createPredicateForSamples(filter: FilterForSamples) throws -> NSPredicate {
|
|
|
219
217
|
return createDatePredicate(dateFilter: dateFilter)
|
|
220
218
|
case .fifth(let w):
|
|
221
219
|
if let w = w.workout as? WorkoutProxy {
|
|
222
|
-
return
|
|
220
|
+
return w.workoutPredicate
|
|
223
221
|
}
|
|
224
222
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
225
223
|
case .sixth(let and):
|
|
@@ -246,7 +244,7 @@ func createPredicateForSamples(filter: Variant_PredicateWithUUID_PredicateWithUU
|
|
|
246
244
|
return createDatePredicate(dateFilter: dateFilter)
|
|
247
245
|
case .fifth(let w):
|
|
248
246
|
if let w = w.workout as? WorkoutProxy {
|
|
249
|
-
return
|
|
247
|
+
return w.workoutPredicate
|
|
250
248
|
}
|
|
251
249
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize workout for filter")
|
|
252
250
|
case .sixth(let and):
|
|
@@ -274,7 +272,7 @@ func deserializeHKQueryAnchor(base64String: String?) throws -> HKQueryAnchor? {
|
|
|
274
272
|
if base64String.isEmpty {
|
|
275
273
|
return nil
|
|
276
274
|
}
|
|
277
|
-
|
|
275
|
+
|
|
278
276
|
// Step 1: Decode the base64 string to a Data object
|
|
279
277
|
guard let data = Data(base64Encoded: base64String) else {
|
|
280
278
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Invalid base64 string: \(base64String)")
|
|
@@ -294,13 +292,12 @@ func deserializeHKQueryAnchor(base64String: String?) throws -> HKQueryAnchor? {
|
|
|
294
292
|
return nil
|
|
295
293
|
}
|
|
296
294
|
|
|
297
|
-
|
|
298
295
|
func initializeCategoryType(_ identifier: String) throws -> HKCategoryType {
|
|
299
296
|
let identifier = HKCategoryTypeIdentifier(rawValue: identifier)
|
|
300
297
|
if let sampleType = HKSampleType.categoryType(forIdentifier: identifier) {
|
|
301
298
|
return sampleType
|
|
302
299
|
}
|
|
303
|
-
|
|
300
|
+
|
|
304
301
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized categoryType with identifier \(identifier)")
|
|
305
302
|
}
|
|
306
303
|
|
|
@@ -308,7 +305,7 @@ func initializeWorkoutActivityType(_ typeIdentifier: UInt) throws -> HKWorkoutAc
|
|
|
308
305
|
if let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) {
|
|
309
306
|
return type
|
|
310
307
|
}
|
|
311
|
-
|
|
308
|
+
|
|
312
309
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized quantityType with identifier \(typeIdentifier)")
|
|
313
310
|
}
|
|
314
311
|
|
|
@@ -318,17 +315,17 @@ func initializeQuantityType(_ identifier: String) throws -> HKQuantityType {
|
|
|
318
315
|
if let sampleType = HKSampleType.quantityType(forIdentifier: identifier) {
|
|
319
316
|
return sampleType
|
|
320
317
|
}
|
|
321
|
-
|
|
318
|
+
|
|
322
319
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized quantityType with identifier \(identifier)")
|
|
323
320
|
}
|
|
324
321
|
|
|
325
322
|
func initializeCorrelationType(_ identifier: String) throws -> HKCorrelationType {
|
|
326
323
|
let identifier = HKCorrelationTypeIdentifier(rawValue: identifier)
|
|
327
|
-
|
|
324
|
+
|
|
328
325
|
if let sampleType = HKSampleType.correlationType(forIdentifier: identifier) {
|
|
329
326
|
return sampleType
|
|
330
327
|
}
|
|
331
|
-
|
|
328
|
+
|
|
332
329
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized correlationType with identifier \(identifier)")
|
|
333
330
|
}
|
|
334
331
|
|
|
@@ -336,7 +333,7 @@ func initializeSeriesType(_ identifier: String) throws -> HKSeriesType {
|
|
|
336
333
|
if let seriesType = HKObjectType.seriesType(forIdentifier: identifier) {
|
|
337
334
|
return seriesType
|
|
338
335
|
}
|
|
339
|
-
|
|
336
|
+
|
|
340
337
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized seriesType with identifier \(identifier)")
|
|
341
338
|
}
|
|
342
339
|
|
|
@@ -344,7 +341,7 @@ func sampleTypeFrom(sampleTypeIdentifier: SampleTypeIdentifier) throws -> HKSamp
|
|
|
344
341
|
if let sampleType = try sampleTypeFromStringNullable(typeIdentifier: sampleTypeIdentifier.stringValue) {
|
|
345
342
|
return sampleType
|
|
346
343
|
}
|
|
347
|
-
|
|
344
|
+
|
|
348
345
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized sampleType with identifier \(sampleTypeIdentifier.stringValue)")
|
|
349
346
|
}
|
|
350
347
|
|
|
@@ -352,7 +349,7 @@ func sampleTypeFrom(sampleTypeIdentifierWriteable: SampleTypeIdentifierWriteable
|
|
|
352
349
|
if let sampleType = try sampleTypeFromStringNullable(typeIdentifier: sampleTypeIdentifierWriteable.stringValue) {
|
|
353
350
|
return sampleType
|
|
354
351
|
}
|
|
355
|
-
|
|
352
|
+
|
|
356
353
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Failed to initialize unrecognized sampleType with identifier \(sampleTypeIdentifierWriteable.stringValue)")
|
|
357
354
|
}
|
|
358
355
|
|
|
@@ -368,7 +365,7 @@ private func sampleTypeFromStringNullable(typeIdentifier: String) throws -> HKSa
|
|
|
368
365
|
if typeIdentifier.starts(with: HKCorrelationTypeIdentifier_PREFIX) {
|
|
369
366
|
return try initializeCorrelationType(typeIdentifier)
|
|
370
367
|
}
|
|
371
|
-
|
|
368
|
+
|
|
372
369
|
if typeIdentifier == HKWorkoutTypeIdentifier {
|
|
373
370
|
return HKSampleType.workoutType()
|
|
374
371
|
}
|
|
@@ -387,12 +384,12 @@ private func sampleTypeFromStringNullable(typeIdentifier: String) throws -> HKSa
|
|
|
387
384
|
if typeIdentifier == HKDataTypeIdentifierHeartbeatSeries {
|
|
388
385
|
return try initializeSeriesType(typeIdentifier)
|
|
389
386
|
}
|
|
390
|
-
|
|
387
|
+
|
|
391
388
|
if typeIdentifier == HKAudiogramTypeIdentifier {
|
|
392
389
|
return HKSampleType.audiogramSampleType()
|
|
393
390
|
}
|
|
394
391
|
}
|
|
395
|
-
|
|
392
|
+
|
|
396
393
|
#if compiler(>=6)
|
|
397
394
|
if #available(iOS 18, *) {
|
|
398
395
|
if typeIdentifier == HKStateOfMindTypeIdentifier {
|
|
@@ -421,7 +418,7 @@ func initializeUUID(_ uuidString: String) throws -> UUID {
|
|
|
421
418
|
if let uuid = UUID(uuidString: uuidString) {
|
|
422
419
|
return uuid
|
|
423
420
|
}
|
|
424
|
-
|
|
421
|
+
|
|
425
422
|
throw RuntimeError.error(withMessage: "[react-native-healthkit] Got invalid UUID: \(uuidString)")
|
|
426
423
|
}
|
|
427
424
|
|
|
@@ -449,14 +446,13 @@ func sampleTypesFromArray(typeIdentifiersWriteable: [SampleTypeIdentifierWriteab
|
|
|
449
446
|
})
|
|
450
447
|
}
|
|
451
448
|
|
|
452
|
-
|
|
453
449
|
// objectType is wider than sampleType, so it uses it under the hood
|
|
454
450
|
func objectTypeFrom(objectTypeIdentifier: ObjectTypeIdentifier) throws -> HKObjectType {
|
|
455
451
|
let typeIdentifier = objectTypeIdentifier.stringValue
|
|
456
452
|
if let sampleType = try sampleTypeFromStringNullable(typeIdentifier: typeIdentifier) {
|
|
457
453
|
return sampleType
|
|
458
454
|
}
|
|
459
|
-
|
|
455
|
+
|
|
460
456
|
if typeIdentifier.starts(with: HKCharacteristicTypeIdentifier_PREFIX) {
|
|
461
457
|
let identifier = HKCharacteristicTypeIdentifier.init(rawValue: typeIdentifier)
|
|
462
458
|
if let type = HKObjectType.characteristicType(forIdentifier: identifier) as HKObjectType? {
|
package/ios/WorkoutProxy.swift
CHANGED
|
@@ -217,6 +217,13 @@ class WorkoutProxy: HybridWorkoutProxySpec {
|
|
|
217
217
|
)
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
var workoutPredicate: NSPredicate {
|
|
221
|
+
get {
|
|
222
|
+
let predicate = HKQuery.predicateForObjects(from: self.workout)
|
|
223
|
+
return predicate
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
220
227
|
var uuid: String {
|
|
221
228
|
get {
|
|
222
229
|
return workout.uuid.uuidString
|