@kingstinct/react-native-healthkit 15.1.0 → 16.0.0

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.
Files changed (107) hide show
  1. package/README.md +5 -1
  2. package/ReactNativeHealthkit.podspec +11 -7
  3. package/app.plugin.js +18 -34
  4. package/app.plugin.ts +25 -50
  5. package/ios/CategoryTypeModule.swift +1 -0
  6. package/ios/CharacteristicTypeModule.swift +7 -6
  7. package/ios/Constants.swift +9 -34
  8. package/ios/CoreModule.swift +72 -41
  9. package/ios/CorrelationTypeModule.swift +3 -2
  10. package/ios/ElectrocardiogramModule.swift +4 -3
  11. package/ios/HeartbeatSeriesModule.swift +4 -3
  12. package/ios/Helpers.swift +10 -243
  13. package/ios/MedicationModule.swift +9 -8
  14. package/ios/MemoryEstimation.swift +1 -0
  15. package/ios/PredicateHelpers.swift +16 -86
  16. package/ios/QuantityTypeModule.swift +3 -2
  17. package/ios/Serializers.swift +16 -67
  18. package/ios/SourceProxy.swift +1 -0
  19. package/ios/StateOfMindModule.swift +3 -2
  20. package/ios/WorkoutProxy.swift +3 -2
  21. package/ios/WorkoutsModule.swift +4 -3
  22. package/lib/commonjs/generated/healthkit.generated.js +2 -0
  23. package/lib/commonjs/healthkit.ios.js +4 -2
  24. package/lib/commonjs/healthkit.js +21 -78
  25. package/lib/commonjs/types/Auth.js +3 -18
  26. package/lib/commonjs/types/Background.js +2 -10
  27. package/lib/commonjs/types/QueryOptions.js +2 -17
  28. package/lib/module/generated/healthkit.generated.js +2 -0
  29. package/lib/module/healthkit.ios.js +2 -0
  30. package/lib/module/healthkit.js +20 -77
  31. package/lib/module/types/Auth.js +1 -18
  32. package/lib/module/types/Background.js +1 -10
  33. package/lib/module/types/QueryOptions.js +1 -17
  34. package/lib/typescript/generated/healthkit.generated.d.ts +4 -1
  35. package/lib/typescript/healthkit.d.ts +3 -2
  36. package/lib/typescript/healthkit.ios.d.ts +11 -9
  37. package/lib/typescript/specs/CoreModule.nitro.d.ts +16 -0
  38. package/lib/typescript/specs/SourceProxy.nitro.d.ts +2 -4
  39. package/lib/typescript/types/Auth.d.ts +1 -16
  40. package/lib/typescript/types/Background.d.ts +1 -9
  41. package/lib/typescript/types/Device.d.ts +1 -13
  42. package/lib/typescript/types/QuantityType.d.ts +1 -1
  43. package/lib/typescript/types/QueryOptions.d.ts +3 -37
  44. package/lib/typescript/types/Shared.d.ts +2 -25
  45. package/lib/typescript/types/Source.d.ts +1 -10
  46. package/lib/typescript/types/Subscriptions.d.ts +2 -3
  47. package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.cpp +8 -0
  48. package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.hpp +68 -0
  49. package/nitrogen/generated/ios/c++/HybridCategoryTypeModuleSpecSwift.hpp +1 -1
  50. package/nitrogen/generated/ios/c++/HybridCoreModuleSpecSwift.hpp +8 -0
  51. package/nitrogen/generated/ios/c++/HybridCorrelationTypeModuleSpecSwift.hpp +1 -1
  52. package/nitrogen/generated/ios/c++/HybridElectrocardiogramModuleSpecSwift.hpp +1 -1
  53. package/nitrogen/generated/ios/c++/HybridHeartbeatSeriesModuleSpecSwift.hpp +1 -1
  54. package/nitrogen/generated/ios/c++/HybridMedicationModuleSpecSwift.hpp +1 -1
  55. package/nitrogen/generated/ios/c++/HybridQuantityTypeModuleSpecSwift.hpp +1 -1
  56. package/nitrogen/generated/ios/c++/HybridStateOfMindModuleSpecSwift.hpp +1 -1
  57. package/nitrogen/generated/ios/swift/CategorySample.swift +8 -8
  58. package/nitrogen/generated/ios/swift/CorrelationSample.swift +8 -8
  59. package/nitrogen/generated/ios/swift/ElectrocardiogramSample.swift +8 -8
  60. package/nitrogen/generated/ios/swift/Func_void_std__unordered_map_std__string__std__chrono__system_clock__time_point_.swift +54 -0
  61. package/nitrogen/generated/ios/swift/HeartbeatSeriesSample.swift +8 -8
  62. package/nitrogen/generated/ios/swift/HybridCoreModuleSpec.swift +1 -0
  63. package/nitrogen/generated/ios/swift/HybridCoreModuleSpec_cxx.swift +25 -0
  64. package/nitrogen/generated/ios/swift/MedicationDoseEvent.swift +8 -8
  65. package/nitrogen/generated/ios/swift/ObjectTypeIdentifier.swift +4 -0
  66. package/nitrogen/generated/ios/swift/QuantitySample.swift +8 -8
  67. package/nitrogen/generated/ios/swift/QuantityTypeIdentifier.swift +4 -0
  68. package/nitrogen/generated/ios/swift/QuantityTypeIdentifierWriteable.swift +4 -0
  69. package/nitrogen/generated/ios/swift/SampleTypeIdentifier.swift +4 -0
  70. package/nitrogen/generated/ios/swift/SampleTypeIdentifierWriteable.swift +4 -0
  71. package/nitrogen/generated/ios/swift/StateOfMindSample.swift +8 -8
  72. package/nitrogen/generated/shared/c++/CategorySample.hpp +7 -7
  73. package/nitrogen/generated/shared/c++/CorrelationSample.hpp +7 -7
  74. package/nitrogen/generated/shared/c++/ElectrocardiogramSample.hpp +7 -7
  75. package/nitrogen/generated/shared/c++/HeartbeatSeriesSample.hpp +7 -7
  76. package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.cpp +1 -0
  77. package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.hpp +3 -1
  78. package/nitrogen/generated/shared/c++/MedicationDoseEvent.hpp +7 -7
  79. package/nitrogen/generated/shared/c++/ObjectTypeIdentifier.hpp +49 -45
  80. package/nitrogen/generated/shared/c++/QuantitySample.hpp +7 -7
  81. package/nitrogen/generated/shared/c++/QuantityTypeIdentifier.hpp +42 -38
  82. package/nitrogen/generated/shared/c++/QuantityTypeIdentifierWriteable.hpp +42 -38
  83. package/nitrogen/generated/shared/c++/SampleTypeIdentifier.hpp +48 -44
  84. package/nitrogen/generated/shared/c++/SampleTypeIdentifierWriteable.hpp +47 -43
  85. package/nitrogen/generated/shared/c++/StateOfMindSample.hpp +7 -7
  86. package/package.json +4 -1
  87. package/src/generated/healthkit-schema.json +8 -0
  88. package/src/generated/healthkit.generated.ts +4 -0
  89. package/src/healthkit.ios.ts +3 -0
  90. package/src/healthkit.ts +27 -84
  91. package/src/specs/CoreModule.nitro.ts +19 -0
  92. package/src/specs/SourceProxy.nitro.ts +2 -4
  93. package/src/test-setup.ts +1 -0
  94. package/src/types/Auth.ts +4 -17
  95. package/src/types/Background.ts +1 -9
  96. package/src/types/Device.ts +1 -13
  97. package/src/types/QuantityType.ts +1 -1
  98. package/src/types/QueryOptions.ts +7 -65
  99. package/src/types/Shared.ts +7 -28
  100. package/src/types/Source.ts +1 -11
  101. package/src/types/Subscriptions.ts +2 -3
  102. package/ios/BackgroundDeliveryManager.swift +0 -284
  103. package/ios/BackgroundLaunchHook.mm +0 -41
  104. package/ios/Bridge.h +0 -10
  105. package/ios/ExceptionCatcher.h +0 -5
  106. package/ios/ExceptionCatcher.mm +0 -32
  107. package/ios/module.modulemap +0 -5
package/ios/Helpers.swift CHANGED
@@ -8,167 +8,14 @@
8
8
  import Foundation
9
9
  import HealthKit
10
10
  import NitroModules
11
+ import ReactNativeHealthkitCore
11
12
 
12
- func parseUnitStringSafe(_ unitString: String) throws -> HKUnit {
13
- var err: NSError?
14
- let unitOut = HKUnitFromStringCatchingExceptions(unitString, &err)
15
-
16
- if let hkUnit = unitOut {
17
- return hkUnit
18
- }
19
-
20
- throw runtimeErrorWithPrefix("Supplied invalid '\(unitString)' as HKUnit")
21
- }
22
-
23
- func getQueryLimit(_ limit: Double) -> Int {
24
- if limit == .infinity || limit <= 0 || limit == .nan || limit == .signalingNaN {
25
- return HKObjectQueryNoLimit
26
- }
27
-
28
- return Int(limit)
29
- }
30
-
31
- struct AnchoredQueryResponse {
32
- var samples: [HKSample]
33
- var deletedSamples: [DeletedSample]
34
- var newAnchor: String
35
- }
36
-
37
- func sampleAnchoredQueryAsync(
38
- sampleType: HKSampleType,
39
- limit: Double,
40
- queryAnchor: String?,
41
- predicate: NSPredicate?
42
- ) async throws -> AnchoredQueryResponse {
43
- let queryAnchor = try deserializeHKQueryAnchor(base64String: queryAnchor)
44
-
45
- return try await withCheckedThrowingContinuation { continuation in
46
- let query = HKAnchoredObjectQuery(
47
- type: sampleType,
48
- predicate: predicate,
49
- anchor: queryAnchor,
50
- limit: getQueryLimit(limit)
51
- ) {
52
- (
53
- _: HKAnchoredObjectQuery, samples: [HKSample]?, deletedSamples: [HKDeletedObject]?,
54
- newAnchor:
55
- HKQueryAnchor?, error: Error?
56
- ) in
57
- DispatchQueue.main.async {
58
- if let error = error {
59
- return continuation.resume(throwing: error)
60
- }
61
-
62
- if let samples = samples, let deletedSamples = deletedSamples,
63
- let newAnchor = serializeAnchor(anchor: newAnchor) {
64
- return continuation.resume(
65
- returning: AnchoredQueryResponse(
66
- samples: samples,
67
- deletedSamples: deletedSamples.map({ deletedSample in
68
- return serializeDeletedSample(sample: deletedSample)
69
- }),
70
- newAnchor: newAnchor
71
- )
72
- )
73
- }
74
-
75
- return continuation.resume(
76
- throwing: runtimeErrorWithPrefix("Unexpected empty response"))
77
- }
78
- }
79
-
80
- store.execute(query)
81
- }
82
- }
83
-
84
- func serializeAnchor(anchor: HKQueryAnchor?) -> String? {
85
- return toBase64(anchor)
86
- }
87
-
88
- func toBase64(_ data: Any?) -> String? {
89
- guard let archivedData = try? NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true) else {
90
- return nil
91
- }
92
-
93
- return archivedData.base64EncodedString()
94
- }
95
-
96
- func sampleQueryAsync(
97
- sampleType: HKSampleType,
98
- limit: Double,
99
- predicate: NSPredicate?,
100
- sortDescriptors: [NSSortDescriptor]?
101
- ) async throws -> [HKSample] {
102
- let limit = getQueryLimit(limit)
103
- return try await withCheckedThrowingContinuation { continuation in
104
- let q = HKSampleQuery(
105
- sampleType: sampleType,
106
- predicate: predicate,
107
- limit: limit,
108
- sortDescriptors: sortDescriptors,
109
- ) { (_: HKSampleQuery, samples: [HKSample]?, error: Error?) in
110
- DispatchQueue.main.async {
111
- if let error = error {
112
- return continuation.resume(throwing: error)
113
- }
114
-
115
- if let samples = samples {
116
- return continuation.resume(returning: samples)
117
- }
118
-
119
- return continuation.resume(
120
- throwing: runtimeErrorWithPrefix("Unexpected empty response"))
121
- }
122
- }
123
-
124
- store.execute(q)
125
- }
126
- }
127
-
128
- func saveAsync(sample: HKObject) async throws -> Bool {
129
- return try await withCheckedThrowingContinuation { continuation in
130
- store.save(sample) { (success: Bool, error: Error?) in
131
- DispatchQueue.main.async {
132
- if let error = error {
133
- continuation.resume(throwing: error)
134
- } else {
135
- continuation.resume(returning: success)
136
- }
137
- }
138
- }
139
- }
140
- }
141
-
142
- func getSortDescriptors(ascending: Bool?) -> [NSSortDescriptor] {
143
- return [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending ?? false)]
144
- }
145
-
146
- func fromBase64(base64String: String?) throws -> Any? {
147
- if let base64String = base64String {
148
- if base64String.isEmpty {
149
- return nil
150
- }
151
-
152
- // Step 1: Decode the base64 string to a Data object
153
- guard let data = Data(base64Encoded: base64String) else {
154
- throw runtimeErrorWithPrefix("Invalid base64 string: \(base64String)")
155
- }
156
-
157
- // Step 2: Use NSKeyedUnarchiver to unarchive the data and create an HKQueryAnchor object
158
- do {
159
- let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
160
- unarchiver.requiresSecureCoding = true
161
- return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
162
- } catch {
163
- throw runtimeErrorWithPrefix(
164
- "Error recreating HKQueryAnchor object: \(error.localizedDescription)")
165
- }
13
+ /// Anchored query results come back with raw HKDeletedObjects; this package
14
+ /// serializes them into its own DeletedSample struct.
15
+ extension AnchoredQueryResponse {
16
+ var deletedSamples: [DeletedSample] {
17
+ return deletedObjects.map { serializeDeletedSample(sample: $0) }
166
18
  }
167
- return nil
168
- }
169
-
170
- func deserializeHKQueryAnchor(base64String: String?) throws -> HKQueryAnchor? {
171
- return try fromBase64(base64String: base64String) as? HKQueryAnchor
172
19
  }
173
20
 
174
21
  func initializeCategoryType(_ identifier: String) throws -> HKCategoryType {
@@ -245,51 +92,7 @@ func sampleTypeFrom(sampleTypeIdentifierWriteable: SampleTypeIdentifierWriteable
245
92
  }
246
93
 
247
94
  private func sampleTypeFromStringNullable(typeIdentifier: String) throws -> HKSampleType? {
248
- if typeIdentifier.starts(with: HKQuantityTypeIdentifier_PREFIX) {
249
- return try initializeQuantityType(typeIdentifier)
250
- }
251
-
252
- if typeIdentifier.starts(with: HKCategoryTypeIdentifier_PREFIX) {
253
- return try initializeCategoryType(typeIdentifier)
254
- }
255
-
256
- if typeIdentifier.starts(with: HKCorrelationTypeIdentifier_PREFIX) {
257
- return try initializeCorrelationType(typeIdentifier)
258
- }
259
-
260
- if typeIdentifier == HKWorkoutTypeIdentifier {
261
- return HKSampleType.workoutType()
262
- }
263
-
264
- if typeIdentifier == HKWorkoutRouteTypeIdentifier {
265
- return try initializeSeriesType(typeIdentifier)
266
- }
267
-
268
- if typeIdentifier == HKAudiogramTypeIdentifier {
269
- return HKObjectType.audiogramSampleType()
270
- }
271
-
272
- if typeIdentifier == HKDataTypeIdentifierHeartbeatSeries {
273
- return try initializeSeriesType(typeIdentifier)
274
- }
275
-
276
- if typeIdentifier == HKAudiogramTypeIdentifier {
277
- return HKSampleType.audiogramSampleType()
278
- }
279
-
280
- if typeIdentifier == HKElectrocardiogramType {
281
- return HKSampleType.electrocardiogramType()
282
- }
283
-
284
- #if compiler(>=6)
285
- if #available(iOS 18, *) {
286
- if typeIdentifier == HKStateOfMindTypeIdentifier {
287
- return HKObjectType.stateOfMindType()
288
- }
289
- }
290
- #endif
291
-
292
- return nil
95
+ return sampleType(fromIdentifier: typeIdentifier)
293
96
  }
294
97
 
295
98
  func objectTypesFromArray(typeIdentifiers: [ObjectTypeIdentifier]) -> Set<HKObjectType> {
@@ -305,14 +108,6 @@ func objectTypesFromArray(typeIdentifiers: [ObjectTypeIdentifier]) -> Set<HKObje
305
108
  return share
306
109
  }
307
110
 
308
- func initializeUUID(_ uuidString: String) throws -> UUID {
309
- if let uuid = UUID(uuidString: uuidString) {
310
- return uuid
311
- }
312
-
313
- throw runtimeErrorWithPrefix("Got invalid UUID: \(uuidString)")
314
- }
315
-
316
111
  func sampleTypesFromArray(typeIdentifiers: [SampleTypeIdentifier]) -> Set<HKSampleType> {
317
112
  return Set(
318
113
  typeIdentifiers.compactMap { typeIdentifier in
@@ -348,7 +143,7 @@ func objectTypeFrom(objectTypeIdentifier: ObjectTypeIdentifier) throws -> HKObje
348
143
  return sampleType
349
144
  }
350
145
 
351
- if typeIdentifier.starts(with: HKCharacteristicTypeIdentifier_PREFIX) {
146
+ if typeIdentifier.starts(with: characteristicTypeIdentifierPrefix) {
352
147
  let identifier = HKCharacteristicTypeIdentifier.init(rawValue: typeIdentifier)
353
148
  if let type = HKObjectType.characteristicType(forIdentifier: identifier) as HKObjectType? {
354
149
  return type
@@ -434,11 +229,11 @@ func anyMapToDictionary(_ anyMap: AnyMap) -> [String: Any] {
434
229
  }
435
230
 
436
231
  func runtimeErrorWithPrefix(_ withMessage: String) -> Error {
437
- return RuntimeError.error(withMessage: "[react-native-healthkit] \(withMessage)")
232
+ return makeRuntimeError(withMessage, prefix: logPrefix)
438
233
  }
439
234
 
440
235
  func warnWithPrefix(_ withMessage: String) {
441
- print("[react-native-healthkit] \(withMessage)")
236
+ logWarning(withMessage, prefix: logPrefix)
442
237
  }
443
238
 
444
239
  func buildStatisticsOptions(statistics: [StatisticsOptions], quantityType: HKQuantityType) -> HKStatisticsOptions {
@@ -483,31 +278,3 @@ func buildStatisticsOptions(statistics: [StatisticsOptions], quantityType: HKQua
483
278
  }
484
279
  return opts
485
280
  }
486
-
487
- private let storeReopenRetryDelay: UInt64 = 1_000_000_000
488
-
489
- /// HealthKit seals its store the moment the device locks and re-opens it a beat
490
- /// after it unlocks, and iOS can resume the app inside that beat. A query that
491
- /// hits the gap fails with `errorDatabaseInaccessible` although protected data is
492
- /// reported available — the store is re-opening, not sealed. Runs `operation`
493
- /// and, on exactly that refusal, gives it one more attempt after a short delay.
494
- /// A refusal while protected data is unavailable is the sealed store itself and
495
- /// is thrown as-is, as is every other error.
496
- func retryingWhileStoreReopens<T>(
497
- _ operation: () async throws -> T
498
- ) async throws -> T {
499
- do {
500
- return try await operation()
501
- } catch {
502
- let nsError = error as NSError
503
- guard nsError.domain == HKError.errorDomain,
504
- nsError.code == HKError.Code.errorDatabaseInaccessible.rawValue
505
- else { throw error }
506
- let protectedDataAvailable = await MainActor.run {
507
- UIApplication.shared.isProtectedDataAvailable
508
- }
509
- guard protectedDataAvailable else { throw error }
510
- try await Task.sleep(nanoseconds: storeReopenRetryDelay)
511
- return try await operation()
512
- }
513
- }
@@ -7,12 +7,13 @@
7
7
 
8
8
  import HealthKit
9
9
  import NitroModules
10
+ import ReactNativeHealthkitCore
10
11
 
11
12
  #if compiler(>=6.2)
12
13
  @available(iOS 26.0, *)
13
14
  func deserializeHKMedicationConceptIdentifier(base64String: String?) throws
14
15
  -> HKHealthConceptIdentifier? {
15
- return try fromBase64(base64String: base64String) as? HKHealthConceptIdentifier
16
+ return try fromBase64(base64String: base64String, as: HKHealthConceptIdentifier.self)
16
17
  }
17
18
 
18
19
  @available(iOS 26.0, *)
@@ -50,7 +51,7 @@ import NitroModules
50
51
  var lookup: [String: (name: String, nickname: String?)] = [:]
51
52
  do {
52
53
  let q = HKUserAnnotatedMedicationQueryDescriptor()
53
- let medications = try await q.result(for: store)
54
+ let medications = try await q.result(for: healthStore)
54
55
  for med in medications {
55
56
  let medPredicate = HKQuery.predicateForMedicationDoseEvent(
56
57
  medicationConceptIdentifier: med.medication.identifier)
@@ -97,16 +98,16 @@ import NitroModules
97
98
  startDate: sample.startDate,
98
99
  endDate: sample.endDate,
99
100
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
101
+ uuid: sample.uuid.uuidString,
102
+ sourceRevision: serializeSourceRevision(sample.sourceRevision),
103
+ device: serializeDevice(hkDevice: sample.device),
100
104
  metadata: {
101
105
  var meta = serializeMetadata(sample.metadata)
102
106
  if let nick = info?.nickname {
103
107
  meta.setString(key: "HKMedicationNickname", value: nick)
104
108
  }
105
109
  return meta
106
- }(),
107
- uuid: sample.uuid.uuidString,
108
- sourceRevision: serializeSourceRevision(sample.sourceRevision),
109
- device: serializeDevice(hkDevice: sample.device)
110
+ }()
110
111
  )
111
112
  }
112
113
 
@@ -152,7 +153,7 @@ import NitroModules
152
153
  if #available(iOS 26.0, *) {
153
154
  let q = HKUserAnnotatedMedicationQueryDescriptor()
154
155
 
155
- let medications = try await q.result(for: store)
156
+ let medications = try await q.result(for: healthStore)
156
157
 
157
158
  return try medications.compactMap({ medication in
158
159
  return try serializeMedication(medication: medication)
@@ -193,7 +194,7 @@ import NitroModules
193
194
  func requestMedicationsAuthorization() -> Promise<Bool> {
194
195
  return Promise.async {
195
196
  if #available(iOS 26.0, *) {
196
- try await store.requestPerObjectReadAuthorization(
197
+ try await healthStore.requestPerObjectReadAuthorization(
197
198
  for: .userAnnotatedMedicationType(), predicate: nil)
198
199
  return true
199
200
  } else {
@@ -9,6 +9,7 @@
9
9
  //
10
10
 
11
11
  import HealthKit
12
+ import ReactNativeHealthkitCore
12
13
 
13
14
  /// Approximate per-object bridging overhead of a Nitro HybridObject that Nitro
14
15
  /// does not account for itself. The generated `*Spec_cxx.swift` adds the Swift
@@ -6,6 +6,7 @@
6
6
  //
7
7
 
8
8
  import HealthKit
9
+ import ReactNativeHealthkitCore
9
10
 
10
11
  func createSourcePredicate(_ sources: [HybridSourceProxySpec]?) -> NSPredicate? {
11
12
  if let sources = sources {
@@ -147,103 +148,32 @@ func createPredicateForWorkout(_ filter: FilterForWorkouts?) -> NSPredicate? {
147
148
  return nil
148
149
  }
149
150
 
150
- func createDatePredicate(_ dateFilter: DateFilter?) -> NSPredicate? {
151
- if let dateFilter = dateFilter {
152
- let strictStartDate = dateFilter.strictStartDate ?? false
153
- let strictEndDate = dateFilter.strictEndDate ?? false
154
-
155
- let options: HKQueryOptions =
156
- strictStartDate && strictEndDate
157
- ? [.strictStartDate, .strictEndDate]
158
- : strictEndDate
159
- ? .strictEndDate
160
- : strictStartDate
161
- ? .strictStartDate
162
- : []
163
-
164
- return HKQuery.predicateForSamples(
165
- withStart: dateFilter.startDate,
166
- end: dateFilter.endDate,
167
- options: options
168
- )
169
- }
170
- return nil
171
- }
172
-
173
- func createUUIDsPredicate(uuids: [String]?) -> NSPredicate? {
174
- if let uuids = uuids {
175
- let uuids = uuids.compactMap { uuidStr -> UUID? in
176
- do {
177
- let uuid = try initializeUUID(uuidStr)
178
- return uuid
179
- } catch {
180
- warnWithPrefix(error.localizedDescription)
181
- return nil
182
- }
183
- }
184
- return HKQuery.predicateForObjects(with: Set(uuids))
185
- }
186
- return nil
187
- }
151
+ // The generated filter structs adopt the core protocols so the shared
152
+ // predicate builders in ReactNativeHealthkitCore can read them.
153
+ extension DateFilter: DateFilterConvertible {}
188
154
 
189
- func createUUIDPredicate(_ uuid: String?) -> NSPredicate? {
190
- if let uuidStr = uuid {
191
- do {
192
- let uuid = try initializeUUID(uuidStr)
193
- return HKQuery.predicateForObject(with: uuid)
194
- } catch {
195
- warnWithPrefix("createUUIDPredicate: \(error.localizedDescription)")
155
+ extension PredicateWithMetadataKey: MetadataPredicateConvertible {
156
+ public var operatorRawValue: Int? {
157
+ guard let operatorType = operatorType else {
196
158
  return nil
197
159
  }
160
+ return Int(operatorType.rawValue)
198
161
  }
199
- return nil
200
- }
201
-
202
- func getComparisonPredicateOperator(_ op: ComparisonPredicateOperator?) -> NSComparisonPredicate
203
- .Operator? {
204
- if let rawValue = op?.rawValue {
205
- if let op = NSComparisonPredicate.Operator.init(rawValue: UInt(rawValue)) {
206
- return op
207
- } else {
208
- warnWithPrefix(
209
- "getComparisonPredicateOperator: Unsupported operator in metadata filter: \(rawValue)")
210
- }
211
- }
212
- return nil
213
- }
214
-
215
- func createMetadataPredicate(_ metadata: PredicateWithMetadataKey?) -> NSPredicate? {
216
- if let metadata = metadata {
217
-
218
- guard let valueVariant = metadata.value else {
219
- return HKQuery.predicateForObjects(withMetadataKey: metadata.withMetadataKey)
220
- }
221
-
222
- let actualValue: Any
223
162
 
224
- switch valueVariant {
163
+ public var metadataValue: Any? {
164
+ switch value {
225
165
  case .first(let boolValue):
226
- actualValue = NSNumber(value: boolValue ? 1 : 0)
166
+ return NSNumber(value: boolValue ? 1 : 0)
227
167
  case .second(let stringValue):
228
- actualValue = stringValue
168
+ return stringValue
229
169
  case .third(let doubleValue):
230
- actualValue = NSNumber(value: doubleValue)
170
+ return NSNumber(value: doubleValue)
231
171
  case .fourth(let dateValue):
232
- actualValue = dateValue
233
- }
234
-
235
- if let operatorType = metadata.operatorType != nil
236
- ? getComparisonPredicateOperator(metadata.operatorType)
237
- : .equalTo {
238
- return HKQuery.predicateForObjects(
239
- withMetadataKey: metadata.withMetadataKey,
240
- operatorType: operatorType,
241
- value: actualValue
242
- )
172
+ return dateValue
173
+ case nil:
174
+ return nil
243
175
  }
244
-
245
176
  }
246
- return nil
247
177
  }
248
178
 
249
179
  func createPredicateForSamplesBase(_ filter: FilterForSamplesBase?) -> NSPredicate? {
@@ -1,5 +1,6 @@
1
1
  import HealthKit
2
2
  import NitroModules
3
+ import ReactNativeHealthkitCore
3
4
 
4
5
  func emptyStatisticsResponse(from: Date?, to: Date?) -> QueryStatisticsResponse {
5
6
  return QueryStatisticsResponse(
@@ -47,7 +48,7 @@ func queryStatisticsForQuantityInternal(
47
48
  }
48
49
  }
49
50
 
50
- store.execute(query)
51
+ healthStore.execute(query)
51
52
  }
52
53
  }
53
54
 
@@ -178,7 +179,7 @@ func queryStatisticsCollectionForQuantityInternal(
178
179
  }
179
180
  }
180
181
 
181
- store.execute(query)
182
+ healthStore.execute(query)
182
183
  }
183
184
  }
184
185
 
@@ -8,12 +8,7 @@
8
8
  import Foundation
9
9
  import HealthKit
10
10
  import NitroModules
11
-
12
- private let metadataDateFormatter: ISO8601DateFormatter = {
13
- let formatter = ISO8601DateFormatter()
14
- formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
15
- return formatter
16
- }()
11
+ import ReactNativeHealthkitCore
17
12
 
18
13
  func serializeQuantityTyped(unit: HKUnit, quantityNullable: HKQuantity?) -> Quantity? {
19
14
  guard let q = quantityNullable else {
@@ -43,10 +38,10 @@ func serializeQuantitySample(sample: HKQuantitySample, unit: HKUnit) throws -> Q
43
38
  startDate: sample.startDate,
44
39
  endDate: sample.endDate,
45
40
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
46
- metadata: serializeMetadata(sample.metadata),
47
41
  uuid: sample.uuid.uuidString,
48
42
  sourceRevision: serializeSourceRevision(sample.sourceRevision),
49
- device: serializeDevice(hkDevice: sample.device)
43
+ device: serializeDevice(hkDevice: sample.device),
44
+ metadata: serializeMetadata(sample.metadata)
50
45
  )
51
46
  }
52
47
  throw runtimeErrorWithPrefix(
@@ -70,10 +65,10 @@ func serializeCategorySample(sample: HKCategorySample) -> CategorySample {
70
65
  startDate: sample.startDate,
71
66
  endDate: sample.endDate,
72
67
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
73
- metadata: serializeMetadata(sample.metadata),
74
68
  uuid: sample.uuid.uuidString,
75
69
  sourceRevision: serializeSourceRevision(sample.sourceRevision),
76
- device: serializeDevice(hkDevice: sample.device)
70
+ device: serializeDevice(hkDevice: sample.device),
71
+ metadata: serializeMetadata(sample.metadata)
77
72
  )
78
73
  }
79
74
 
@@ -223,59 +218,19 @@ func serializeUnknownQuantity(quantity: HKQuantity) -> [String: AnyValue]? {
223
218
  return nil
224
219
  }
225
220
 
226
- func serializeMetadata(_ metadata: [String: Any]?) -> AnyMap {
227
- let serialized = AnyMap()
228
- if let m = metadata {
229
- for item in m {
230
- if let number = item.value as? NSNumber {
231
- if isKnownBooleanMetadataKey(item.key) {
232
- serialized.setBoolean(key: item.key, value: number.boolValue)
233
- } else if isKnownNumericMetadataKey(item.key) {
234
- serialized.setDouble(key: item.key, value: number.doubleValue)
235
- } else if CFGetTypeID(number) == CFBooleanGetTypeID() {
236
- serialized.setBoolean(key: item.key, value: number.boolValue)
237
- } else {
238
- serialized.setDouble(key: item.key, value: number.doubleValue)
239
- }
240
- continue
241
- }
242
-
243
- if let bool = item.value as? Bool {
244
- serialized.setBoolean(key: item.key, value: bool)
245
- continue
246
- }
247
-
248
- if let str = item.value as? String {
249
- serialized.setString(key: item.key, value: str)
250
- continue
251
- }
252
-
253
- if let double = item.value as? Double {
254
- serialized.setDouble(key: item.key, value: double)
255
- continue
256
- }
257
-
258
- if let date = item.value as? Date {
259
- serialized.setString(
260
- key: item.key,
261
- value: metadataDateFormatter.string(from: date)
262
- )
263
- continue
264
- }
265
-
266
- if let quantity = item.value as? HKQuantity {
267
- if let s = serializeUnknownQuantity(quantity: quantity) {
268
- serialized.setObject(key: item.key, value: s)
269
- }
270
- continue
271
- }
272
-
273
- if let dict = item.value as? [String: AnyValue] {
274
- serialized.setObject(key: item.key, value: dict)
275
- }
221
+ private let metadataSerializationOptions = MetadataSerializationOptions(
222
+ isBooleanKey: isKnownBooleanMetadataKey,
223
+ isNumericKey: isKnownNumericMetadataKey,
224
+ serializeQuantity: { quantity in
225
+ if let serialized = serializeUnknownQuantity(quantity: quantity) {
226
+ return .object(serialized)
276
227
  }
228
+ return nil
277
229
  }
278
- return serialized
230
+ )
231
+
232
+ func serializeMetadata(_ metadata: [String: Any]?) -> AnyMap {
233
+ return ReactNativeHealthkitCore.serializeMetadata(metadata, options: metadataSerializationOptions)
279
234
  }
280
235
 
281
236
  func serializeDevice(hkDevice: HKDevice?) -> Device? {
@@ -295,12 +250,6 @@ func serializeDevice(hkDevice: HKDevice?) -> Device? {
295
250
  )
296
251
  }
297
252
 
298
- func serializeOperatingSystemVersion(_ version: OperatingSystemVersion) -> String {
299
- let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
300
-
301
- return versionString
302
- }
303
-
304
253
  func serializeSourceRevision(_ hkSourceRevision: HKSourceRevision) -> SourceRevision {
305
254
  return SourceRevision(
306
255
  source: serializeSourceStruct(hkSourceRevision.source),
@@ -7,6 +7,7 @@
7
7
 
8
8
  import HealthKit
9
9
  import NitroModules
10
+ import ReactNativeHealthkitCore
10
11
 
11
12
  class SourceProxy: HybridSourceProxySpec {
12
13
  func toJSON(key: String?) throws -> Source {
@@ -1,5 +1,6 @@
1
1
  import HealthKit
2
2
  import NitroModules
3
+ import ReactNativeHealthkitCore
3
4
 
4
5
  #if compiler(>=6)
5
6
 
@@ -40,10 +41,10 @@ import NitroModules
40
41
  startDate: sample.startDate,
41
42
  endDate: sample.endDate,
42
43
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
43
- metadata: serializeMetadata(sample.metadata),
44
44
  uuid: sample.uuid.uuidString,
45
45
  sourceRevision: serializeSourceRevision(sample.sourceRevision),
46
- device: serializeDevice(hkDevice: sample.device)
46
+ device: serializeDevice(hkDevice: sample.device),
47
+ metadata: serializeMetadata(sample.metadata)
47
48
  )
48
49
  }
49
50
 
@@ -8,6 +8,7 @@
8
8
  import CoreLocation
9
9
  import HealthKit
10
10
  import NitroModules
11
+ import ReactNativeHealthkitCore
11
12
 
12
13
  @available(iOS 17.0.0, *)
13
14
  func getWorkoutPlanInternal(workout: HKWorkout) async throws -> WorkoutPlan? {
@@ -84,7 +85,7 @@ func getRouteLocations(
84
85
  }
85
86
  }
86
87
 
87
- store.execute(query)
88
+ healthStore.execute(query)
88
89
  }
89
90
  }
90
91
 
@@ -166,7 +167,7 @@ func saveWorkoutRouteInternal(
166
167
  // create CLLocations and return if locations are empty
167
168
  let clLocations = mapLocations(from: locations)
168
169
  let routeBuilder = HKWorkoutRouteBuilder(
169
- healthStore: store,
170
+ healthStore: healthStore,
170
171
  device: nil
171
172
  )
172
173
  try await routeBuilder.insertRouteData(clLocations)