@kingstinct/react-native-healthkit 8.4.0 → 8.5.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.
package/ios/Helpers.swift CHANGED
@@ -18,7 +18,8 @@ func limitOrNilIfZero(limit: Int) -> Int {
18
18
 
19
19
  func createPredicate(from: Date?, to: Date?) -> NSPredicate? {
20
20
  if from != nil || to != nil {
21
- return HKQuery.predicateForSamples(withStart: from, end: to, options: [.strictEndDate, .strictStartDate])
21
+ return HKQuery.predicateForSamples(
22
+ withStart: from, end: to, options: [.strictEndDate, .strictStartDate])
22
23
  } else {
23
24
  return nil
24
25
  }
@@ -29,23 +30,23 @@ func getSortDescriptors(ascending: Bool) -> [NSSortDescriptor] {
29
30
  }
30
31
 
31
32
  func base64StringToHKQueryAnchor(base64String: String) -> HKQueryAnchor? {
32
- // Step 1: Decode the base64 string to a Data object
33
- guard let data = Data(base64Encoded: base64String) else {
34
- print("Error: Invalid base64 string")
35
- return nil
36
- }
37
-
38
- // Step 2: Use NSKeyedUnarchiver to unarchive the data and create an HKQueryAnchor object
39
- do {
40
- let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
41
- unarchiver.requiresSecureCoding = true
42
- let anchor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
43
-
44
- return anchor as? HKQueryAnchor
45
- } catch {
46
- print("Error: Unable to unarchive HKQueryAnchor object: \(error)")
47
- return nil
48
- }
33
+ // Step 1: Decode the base64 string to a Data object
34
+ guard let data = Data(base64Encoded: base64String) else {
35
+ print("Error: Invalid base64 string")
36
+ return nil
37
+ }
38
+
39
+ // Step 2: Use NSKeyedUnarchiver to unarchive the data and create an HKQueryAnchor object
40
+ do {
41
+ let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
42
+ unarchiver.requiresSecureCoding = true
43
+ let anchor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
44
+
45
+ return anchor as? HKQueryAnchor
46
+ } catch {
47
+ print("Error: Unable to unarchive HKQueryAnchor object: \(error)")
48
+ return nil
49
+ }
49
50
  }
50
51
 
51
52
  func sampleTypeFromString(typeIdentifier: String) -> HKSampleType? {
@@ -102,7 +103,7 @@ func sampleTypesFromDictionary(typeIdentifiers: NSDictionary) -> Set<HKSampleTyp
102
103
  if item.value as! Bool {
103
104
  let sampleType = sampleTypeFromString(typeIdentifier: item.key as! String)
104
105
  if sampleType != nil {
105
- share.insert(sampleType!)
106
+ share.insert(sampleType!)
106
107
  }
107
108
  }
108
109
  }
@@ -134,11 +135,13 @@ func objectTypeFromString(typeIdentifier: String) -> HKObjectType? {
134
135
  return HKObjectType.activitySummaryType()
135
136
  }
136
137
 
137
- if #available(iOS 18, *) {
138
- if typeIdentifier == HKStateOfMindTypeIdentifier {
139
- return HKObjectType.stateOfMindType()
140
- }
141
- }
138
+ #if compiler(>=6)
139
+ if #available(iOS 18, *) {
140
+ if typeIdentifier == HKStateOfMindTypeIdentifier {
141
+ return HKObjectType.stateOfMindType()
142
+ }
143
+ }
144
+ #endif
142
145
 
143
146
  if #available(iOS 13, *) {
144
147
  if typeIdentifier == HKAudiogramTypeIdentifier {
@@ -223,23 +226,24 @@ func serializeQuantityIfExists(unit: HKUnit, quantity: HKQuantity?) -> [String:
223
226
  return serializeQuantity(unit: unit, quantity: quantity)
224
227
  }
225
228
 
226
- func serializeStatisticIfExists(unit: HKUnit, quantity: HKQuantity?, stats: HKStatistics) -> [String: Any]? {
229
+ func serializeStatisticIfExists(unit: HKUnit, quantity: HKQuantity?, stats: HKStatistics)
230
+ -> [String: Any]? {
227
231
  guard let quantity = quantity else { return nil }
228
232
  return serializeStatistic(unit: unit, quantity: quantity, stats: stats)
229
233
  }
230
234
 
231
235
  func parseWorkoutConfiguration(_ dict: NSDictionary) -> HKWorkoutConfiguration {
232
- let configuration = HKWorkoutConfiguration()
236
+ let configuration = HKWorkoutConfiguration()
233
237
 
234
- if let activityTypeRaw = dict[HKWorkoutActivityTypePropertyName] as? UInt,
235
- let activityType = HKWorkoutActivityType(rawValue: activityTypeRaw) {
236
- configuration.activityType = activityType
237
- }
238
+ if let activityTypeRaw = dict[HKWorkoutActivityTypePropertyName] as? UInt,
239
+ let activityType = HKWorkoutActivityType(rawValue: activityTypeRaw) {
240
+ configuration.activityType = activityType
241
+ }
238
242
 
239
- if let locationTypeRaw = dict[HKWorkoutSessionLocationTypePropertyName] as? Int,
240
- let locationType = HKWorkoutSessionLocationType(rawValue: locationTypeRaw) {
241
- configuration.locationType = locationType
242
- }
243
+ if let locationTypeRaw = dict[HKWorkoutSessionLocationTypePropertyName] as? Int,
244
+ let locationType = HKWorkoutSessionLocationType(rawValue: locationTypeRaw) {
245
+ configuration.locationType = locationType
246
+ }
243
247
 
244
- return configuration
248
+ return configuration
245
249
  }
@@ -1,8 +1,8 @@
1
- import HealthKit
2
1
  import CoreLocation
2
+ import HealthKit
3
3
 
4
4
  #if canImport(WorkoutKit)
5
- import WorkoutKit
5
+ import WorkoutKit
6
6
  #endif
7
7
 
8
8
  @objc(ReactNativeHealthkit)
@@ -87,10 +87,11 @@ class ReactNativeHealthkit: RCTEventEmitter {
87
87
  let share = sampleTypesFromDictionary(typeIdentifiers: toShare)
88
88
  let toRead = objectTypesFromDictionary(typeIdentifiers: read)
89
89
 
90
- store.getRequestStatusForAuthorization(toShare: share, read: toRead) { (
91
- status: HKAuthorizationRequestStatus,
92
- error: Error?
93
- ) in
90
+ store.getRequestStatusForAuthorization(toShare: share, read: toRead) {
91
+ (
92
+ status: HKAuthorizationRequestStatus,
93
+ error: Error?
94
+ ) in
94
95
  guard let err = error else {
95
96
  return resolve(status.rawValue)
96
97
  }
@@ -116,7 +117,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
116
117
  }
117
118
  }
118
119
 
119
- store.preferredUnits(for: quantityTypes) { (typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
120
+ store.preferredUnits(for: quantityTypes) {
121
+ (typePerUnits: [HKQuantityType: HKUnit], _: Error?) in
120
122
  let dic: NSMutableDictionary = NSMutableDictionary()
121
123
 
122
124
  for typePerUnit in typePerUnits {
@@ -291,7 +293,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
291
293
 
292
294
  let samplePredicate = HKQuery.predicateForObject(with: sampleUuid)
293
295
 
294
- store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
296
+ store.deleteObjects(of: sampleType, predicate: samplePredicate) {
297
+ (success: Bool, _: Int, error: Error?) in
295
298
  guard let err = error else {
296
299
  return resolve(success)
297
300
  }
@@ -323,7 +326,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
323
326
  options: HKQueryOptions.strictStartDate
324
327
  )
325
328
 
326
- store.deleteObjects(of: sampleType, predicate: samplePredicate) { (success: Bool, _: Int, error: Error?) in
329
+ store.deleteObjects(of: sampleType, predicate: samplePredicate) {
330
+ (success: Bool, _: Int, error: Error?) in
327
331
  guard let err = error else {
328
332
  return resolve(success)
329
333
  }
@@ -421,7 +425,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
421
425
  }
422
426
 
423
427
  guard let type = HKWorkoutActivityType.init(rawValue: typeIdentifier) else {
424
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
428
+ return reject(
429
+ TYPE_IDENTIFIER_ERROR,
430
+ "Failed to initialize HKWorkoutActivityType " + typeIdentifier.description, nil)
425
431
  }
426
432
 
427
433
  // if start and end both exist, ensure that start date is before end date
@@ -572,13 +578,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
572
578
  var clLocations: [CLLocation] = []
573
579
  for location in locations {
574
580
  guard let latitude = location["latitude"] as? CLLocationDegrees,
575
- let longitude = location["longitude"] as? CLLocationDegrees,
576
- let altitude = location["altitude"] as? CLLocationDistance,
577
- let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
578
- let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
579
- let course = location["course"] as? CLLocationDirection,
580
- let speed = location["speed"] as? CLLocationSpeed,
581
- let timestamp = location["timestamp"] as? String else {
581
+ let longitude = location["longitude"] as? CLLocationDegrees,
582
+ let altitude = location["altitude"] as? CLLocationDistance,
583
+ let horizontalAccuracy = location["horizontalAccuracy"] as? CLLocationAccuracy,
584
+ let verticalAccuracy = location["verticalAccuracy"] as? CLLocationAccuracy,
585
+ let course = location["course"] as? CLLocationDirection,
586
+ let speed = location["speed"] as? CLLocationSpeed,
587
+ let timestamp = location["timestamp"] as? String
588
+ else {
582
589
  continue
583
590
  }
584
591
 
@@ -698,7 +705,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
698
705
  return reject("UpdateFrequency not valid", "UpdateFrequency not valid", nil)
699
706
  }
700
707
 
701
- store.enableBackgroundDelivery(for: sampleType, frequency: frequency ) { (success, error) in
708
+ store.enableBackgroundDelivery(for: sampleType, frequency: frequency) { (success, error) in
702
709
  guard let err = error else {
703
710
  return resolve(success)
704
711
  }
@@ -767,13 +774,17 @@ class ReactNativeHealthkit: RCTEventEmitter {
767
774
 
768
775
  let queryId = UUID().uuidString
769
776
 
770
- func responder(query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) {
777
+ func responder(
778
+ query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?
779
+ ) {
771
780
  if error == nil {
772
781
  DispatchQueue.main.async {
773
782
  if self.bridge != nil && self.bridge.isValid {
774
- self.sendEvent(withName: "onChange", body: [
775
- "typeIdentifier": typeIdentifier
776
- ])
783
+ self.sendEvent(
784
+ withName: "onChange",
785
+ body: [
786
+ "typeIdentifier": typeIdentifier
787
+ ])
777
788
  }
778
789
 
779
790
  }
@@ -784,7 +795,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
784
795
  let query = HKObserverQuery(
785
796
  sampleType: sampleType,
786
797
  predicate: predicate
787
- ) { (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?) in
798
+ ) {
799
+ (query: HKObserverQuery, handler: @escaping HKObserverQueryCompletionHandler, error: Error?)
800
+ in
788
801
  guard let err = error else {
789
802
  return responder(query: query, handler: handler, error: error)
790
803
  }
@@ -894,34 +907,38 @@ class ReactNativeHealthkit: RCTEventEmitter {
894
907
 
895
908
  let unit = HKUnit.init(from: unitString)
896
909
  if let averageQuantity = gottenStats.averageQuantity() {
897
- dic.updateValue(serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
910
+ dic.updateValue(
911
+ serializeQuantity(unit: unit, quantity: averageQuantity), forKey: "averageQuantity")
898
912
  }
899
913
  if let maximumQuantity = gottenStats.maximumQuantity() {
900
- dic.updateValue(serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
914
+ dic.updateValue(
915
+ serializeQuantity(unit: unit, quantity: maximumQuantity), forKey: "maximumQuantity")
901
916
  }
902
917
  if let minimumQuantity = gottenStats.minimumQuantity() {
903
- dic.updateValue(serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
918
+ dic.updateValue(
919
+ serializeQuantity(unit: unit, quantity: minimumQuantity), forKey: "minimumQuantity")
904
920
  }
905
921
  if let sumQuantity = gottenStats.sumQuantity() {
906
922
  dic.updateValue(serializeQuantity(unit: unit, quantity: sumQuantity), forKey: "sumQuantity")
907
923
  }
908
924
  if #available(iOS 12, *) {
909
925
  if let mostRecent = gottenStats.mostRecentQuantity() {
910
- dic.updateValue(serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
926
+ dic.updateValue(
927
+ serializeQuantity(unit: unit, quantity: mostRecent), forKey: "mostRecentQuantity")
911
928
  }
912
929
 
913
930
  if let mostRecentDateInterval = gottenStats.mostRecentQuantityDateInterval() {
914
- dic.updateValue([
915
- "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
916
- "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
917
- ], forKey: "mostRecentQuantityDateInterval")
931
+ dic.updateValue(
932
+ [
933
+ "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
934
+ "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
935
+ ], forKey: "mostRecentQuantityDateInterval")
918
936
  }
919
937
  }
920
938
  if #available(iOS 13, *) {
921
939
  let durationUnit = HKUnit.second()
922
- if let duration = gottenStats.duration() {
923
- dic.updateValue(serializeQuantity(unit: durationUnit, quantity: duration), forKey: "duration")
924
- }
940
+ dic["duration"] = serializeQuantityIfExists(
941
+ unit: durationUnit, quantity: gottenStats.duration())
925
942
  }
926
943
 
927
944
  resolve(dic)
@@ -930,83 +947,97 @@ class ReactNativeHealthkit: RCTEventEmitter {
930
947
  store.execute(query)
931
948
  }
932
949
 
933
- @objc(queryStatisticsCollectionForQuantity:unitString:options:anchorDate:interval:startDate:endDate:resolve:reject:)
950
+ @objc(
951
+ queryStatisticsCollectionForQuantity:unitString:options:anchorDate:interval:startDate:endDate:
952
+ resolve:reject:
953
+ )
934
954
  func queryStatisticsCollectionForQuantity(
935
- typeIdentifier: String,
936
- unitString: String,
937
- options: NSArray,
938
- anchorDate: Date,
939
- interval: NSDictionary,
940
- startDate: Date,
941
- endDate: Date,
942
- resolve: @escaping RCTPromiseResolveBlock,
943
- reject: @escaping RCTPromiseRejectBlock
955
+ typeIdentifier: String,
956
+ unitString: String,
957
+ options: NSArray,
958
+ anchorDate: Date,
959
+ interval: NSDictionary,
960
+ startDate: Date,
961
+ endDate: Date,
962
+ resolve: @escaping RCTPromiseResolveBlock,
963
+ reject: @escaping RCTPromiseRejectBlock
944
964
  ) {
945
- guard let store = _store else {
946
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
947
- }
948
-
949
- guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier(rawValue: typeIdentifier)) else {
950
- return reject(TYPE_IDENTIFIER_ERROR, "Failed to initialize quantity type for identifier: \(typeIdentifier)", nil)
951
- }
952
-
953
- let opts = hkStatisticsOptionsFromOptions(options)
954
- let intervalComponents = componentsFromInterval(interval)
965
+ guard let store = _store else {
966
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
967
+ }
955
968
 
956
- let query = HKStatisticsCollectionQuery(
957
- quantityType: quantityType,
958
- quantitySamplePredicate: nil, // We will use enumerateStatistics to filter with date
959
- options: opts,
960
- anchorDate: anchorDate,
961
- intervalComponents: intervalComponents
962
- )
969
+ guard
970
+ let quantityType = HKObjectType.quantityType(
971
+ forIdentifier: HKQuantityTypeIdentifier(rawValue: typeIdentifier))
972
+ else {
973
+ return reject(
974
+ TYPE_IDENTIFIER_ERROR,
975
+ "Failed to initialize quantity type for identifier: \(typeIdentifier)", nil)
976
+ }
963
977
 
964
- query.initialResultsHandler = { _, statsCollection, error in
965
- if let error = error {
966
- return reject(QUERY_ERROR, error.localizedDescription, error)
967
- }
978
+ let opts = hkStatisticsOptionsFromOptions(options)
979
+ let intervalComponents = componentsFromInterval(interval)
968
980
 
969
- var results = [[String: [String: Any]?]]()
981
+ let query = HKStatisticsCollectionQuery(
982
+ quantityType: quantityType,
983
+ quantitySamplePredicate: nil, // We will use enumerateStatistics to filter with date
984
+ options: opts,
985
+ anchorDate: anchorDate,
986
+ intervalComponents: intervalComponents
987
+ )
970
988
 
971
- guard let collection = statsCollection else {
972
- return resolve(results)
973
- }
989
+ query.initialResultsHandler = { _, statsCollection, error in
990
+ if let error = error {
991
+ return reject(QUERY_ERROR, error.localizedDescription, error)
992
+ }
974
993
 
975
- let unit = HKUnit(from: unitString)
994
+ var results = [[String: [String: Any]?]]()
976
995
 
977
- collection.enumerateStatistics(from: startDate, to: endDate) { stats, _ in
978
- var dic = [String: [String: Any]?]()
996
+ guard let collection = statsCollection else {
997
+ return resolve(results)
998
+ }
979
999
 
980
- let startDate = self._dateFormatter.string(from: stats.startDate)
981
- let endDate = self._dateFormatter.string(from: stats.endDate)
1000
+ let unit = HKUnit(from: unitString)
982
1001
 
983
- dic["averageQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.averageQuantity())
984
- dic["maximumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.maximumQuantity())
985
- dic["minimumQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.minimumQuantity())
986
- dic["sumQuantity"] = serializeStatisticIfExists(unit: unit, quantity: stats.sumQuantity(), stats: stats)
1002
+ collection.enumerateStatistics(from: startDate, to: endDate) { stats, _ in
1003
+ var dic = [String: [String: Any]?]()
987
1004
 
988
- if #available(iOS 12, *) {
989
- dic["mostRecentQuantity"] = serializeQuantityIfExists(unit: unit, quantity: stats.mostRecentQuantity())
990
- if let mostRecentDateInterval = stats.mostRecentQuantityDateInterval() {
991
- dic["mostRecentQuantityDateInterval"] = [
992
- "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
993
- "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
994
- ]
995
- }
996
- }
1005
+ let startDate = self._dateFormatter.string(from: stats.startDate)
1006
+ let endDate = self._dateFormatter.string(from: stats.endDate)
997
1007
 
998
- if #available(iOS 13, *) {
999
- let durationUnit = HKUnit.second()
1000
- dic["duration"] = serializeQuantityIfExists(unit: durationUnit, quantity: stats.duration())
1001
- }
1008
+ dic["averageQuantity"] = serializeQuantityIfExists(
1009
+ unit: unit, quantity: stats.averageQuantity())
1010
+ dic["maximumQuantity"] = serializeQuantityIfExists(
1011
+ unit: unit, quantity: stats.maximumQuantity())
1012
+ dic["minimumQuantity"] = serializeQuantityIfExists(
1013
+ unit: unit, quantity: stats.minimumQuantity())
1014
+ dic["sumQuantity"] = serializeStatisticIfExists(
1015
+ unit: unit, quantity: stats.sumQuantity(), stats: stats)
1002
1016
 
1003
- results.append(dic)
1017
+ if #available(iOS 12, *) {
1018
+ dic["mostRecentQuantity"] = serializeQuantityIfExists(
1019
+ unit: unit, quantity: stats.mostRecentQuantity())
1020
+ if let mostRecentDateInterval = stats.mostRecentQuantityDateInterval() {
1021
+ dic["mostRecentQuantityDateInterval"] = [
1022
+ "start": self._dateFormatter.string(from: mostRecentDateInterval.start),
1023
+ "end": self._dateFormatter.string(from: mostRecentDateInterval.end)
1024
+ ]
1004
1025
  }
1026
+ }
1005
1027
 
1006
- resolve(results)
1028
+ if #available(iOS 13, *) {
1029
+ let durationUnit = HKUnit.second()
1030
+ dic["duration"] = serializeQuantityIfExists(
1031
+ unit: durationUnit, quantity: stats.duration())
1032
+ }
1033
+
1034
+ results.append(dic)
1007
1035
  }
1008
1036
 
1009
- store.execute(query)
1037
+ resolve(results)
1038
+ }
1039
+
1040
+ store.execute(query)
1010
1041
  }
1011
1042
 
1012
1043
  func mapWorkout(
@@ -1021,9 +1052,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
1021
1052
  "uuid": workout.uuid.uuidString,
1022
1053
  "device": serializeDevice(_device: workout.device) as Any,
1023
1054
  "duration": workout.duration,
1024
- "totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance) as Any,
1025
- "totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned) as Any,
1026
- "totalSwimmingStrokeCount": serializeQuantity(unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
1055
+ "totalDistance": serializeQuantity(unit: distanceUnit, quantity: workout.totalDistance)
1056
+ as Any,
1057
+ "totalEnergyBurned": serializeQuantity(unit: energyUnit, quantity: workout.totalEnergyBurned)
1058
+ as Any,
1059
+ "totalSwimmingStrokeCount": serializeQuantity(
1060
+ unit: HKUnit.count(), quantity: workout.totalSwimmingStrokeCount) as Any,
1027
1061
  "workoutActivityType": workout.workoutActivityType.rawValue,
1028
1062
  "startDate": startDate,
1029
1063
  "endDate": endDate,
@@ -1039,7 +1073,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
1039
1073
  let eventStartDate = self._dateFormatter.string(from: event.dateInterval.start)
1040
1074
  let eventEndDate = self._dateFormatter.string(from: event.dateInterval.end)
1041
1075
  let eventDict: [String: Any] = [
1042
- "type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
1076
+ "type": event.type.rawValue, // https://developer.apple.com/documentation/healthkit/hkworkouteventtype
1043
1077
  "startDate": eventStartDate,
1044
1078
  "endDate": eventEndDate
1045
1079
  ]
@@ -1078,7 +1112,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
1078
1112
  dict["activities"] = activitiesArray
1079
1113
 
1080
1114
  if #available(iOS 11, *) {
1081
- dict.setValue(serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed), forKey: "totalFlightsClimbed")
1115
+ dict.setValue(
1116
+ serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed),
1117
+ forKey: "totalFlightsClimbed")
1082
1118
  }
1083
1119
  return dict
1084
1120
  }
@@ -1167,13 +1203,16 @@ class ReactNativeHealthkit: RCTEventEmitter {
1167
1203
 
1168
1204
  let actualAnchor = deserializeHKQueryAnchor(anchor: anchor)
1169
1205
 
1170
- let q = HKAnchoredObjectQuery(type: .workoutType(), predicate: predicate, anchor: actualAnchor, limit: limit) { (
1171
- _: HKAnchoredObjectQuery,
1172
- s: [HKSample]?,
1173
- deletedSamples: [HKDeletedObject]?,
1174
- newAnchor: HKQueryAnchor?,
1175
- error: Error?
1176
- ) in
1206
+ let q = HKAnchoredObjectQuery(
1207
+ type: .workoutType(), predicate: predicate, anchor: actualAnchor, limit: limit
1208
+ ) {
1209
+ (
1210
+ _: HKAnchoredObjectQuery,
1211
+ s: [HKSample]?,
1212
+ deletedSamples: [HKDeletedObject]?,
1213
+ newAnchor: HKQueryAnchor?,
1214
+ error: Error?
1215
+ ) in
1177
1216
  guard let err = error else {
1178
1217
  guard let samples = s else {
1179
1218
  return resolve([])
@@ -1246,7 +1285,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
1246
1285
 
1247
1286
  for s in samples {
1248
1287
  if let sample = s as? HKQuantitySample {
1249
- let serialized = serializeQuantitySample(sample: sample, unit: HKUnit.init(from: unitString))
1288
+ let serialized = serializeQuantitySample(
1289
+ sample: sample, unit: HKUnit.init(from: unitString))
1250
1290
 
1251
1291
  arr.add(serialized)
1252
1292
  }
@@ -1365,7 +1405,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
1365
1405
  let predicate = createPredicate(from: from, to: to)
1366
1406
  let limit = limitOrNilIfZero(limit: limit)
1367
1407
 
1368
- let q = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: limit, sortDescriptors: getSortDescriptors(ascending: ascending)) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1408
+ let q = HKSampleQuery(
1409
+ sampleType: sampleType, predicate: predicate, limit: limit,
1410
+ sortDescriptors: getSortDescriptors(ascending: ascending)
1411
+ ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
1369
1412
  guard let err = error else {
1370
1413
  guard let samples = sample else {
1371
1414
  return resolve([])
@@ -1420,13 +1463,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
1420
1463
  predicate: predicate,
1421
1464
  anchor: actualAnchor,
1422
1465
  limit: limit
1423
- ) { (
1424
- _: HKAnchoredObjectQuery,
1425
- s: [HKSample]?,
1426
- deletedSamples: [HKDeletedObject]?,
1427
- newAnchor: HKQueryAnchor?,
1428
- error: Error?
1429
- ) in
1466
+ ) {
1467
+ (
1468
+ _: HKAnchoredObjectQuery,
1469
+ s: [HKSample]?,
1470
+ deletedSamples: [HKDeletedObject]?,
1471
+ newAnchor: HKQueryAnchor?,
1472
+ error: Error?
1473
+ ) in
1430
1474
  guard let err = error else {
1431
1475
  guard let samples = s else {
1432
1476
  return resolve([])
@@ -1482,13 +1526,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
1482
1526
  predicate: predicate,
1483
1527
  anchor: anchor != "" ? base64StringToHKQueryAnchor(base64String: anchor) : nil,
1484
1528
  limit: limit
1485
- ) { (
1486
- _: HKAnchoredObjectQuery,
1487
- s: [HKSample]?,
1488
- deletedSamples: [HKDeletedObject]?,
1489
- newAnchor: HKQueryAnchor?,
1490
- error: Error?
1491
- ) in
1529
+ ) {
1530
+ (
1531
+ _: HKAnchoredObjectQuery,
1532
+ s: [HKSample]?,
1533
+ deletedSamples: [HKDeletedObject]?,
1534
+ newAnchor: HKQueryAnchor?,
1535
+ error: Error?
1536
+ ) in
1492
1537
  guard let err = error else {
1493
1538
  guard let samples = s else {
1494
1539
  return resolve([])
@@ -1536,11 +1581,12 @@ class ReactNativeHealthkit: RCTEventEmitter {
1536
1581
  let query = HKSourceQuery(
1537
1582
  sampleType: type as! HKSampleType,
1538
1583
  samplePredicate: nil
1539
- ) { (
1540
- _: HKSourceQuery,
1541
- source: Set<HKSource>?,
1542
- error: Error?
1543
- ) in
1584
+ ) {
1585
+ (
1586
+ _: HKSourceQuery,
1587
+ source: Set<HKSource>?,
1588
+ error: Error?
1589
+ ) in
1544
1590
  guard let err = error else {
1545
1591
  guard let sources = source else {
1546
1592
  return resolve([])
@@ -1585,8 +1631,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
1585
1631
  }
1586
1632
 
1587
1633
  @available(iOS 13.0.0, *)
1588
- func getWorkoutByID(store: HKHealthStore,
1589
- workoutUUID: UUID) async -> HKWorkout? {
1634
+ func getWorkoutByID(
1635
+ store: HKHealthStore,
1636
+ workoutUUID: UUID
1637
+ ) async -> HKWorkout? {
1590
1638
  let workoutPredicate = HKQuery.predicateForObject(with: workoutUUID)
1591
1639
 
1592
1640
  let samples = try! await withCheckedThrowingContinuation {
@@ -1698,28 +1746,30 @@ class ReactNativeHealthkit: RCTEventEmitter {
1698
1746
  func getSerializedWorkoutLocations(
1699
1747
  store: HKHealthStore,
1700
1748
  workoutUUID: UUID
1701
- ) async -> [Dictionary<String, Any>]? {
1749
+ ) async -> [[String: Any]]? {
1702
1750
  let routes = await _getWorkoutRoutes(
1703
1751
  store: store,
1704
1752
  workoutUUID: workoutUUID
1705
1753
  )
1706
1754
 
1707
- var allRoutes: [Dictionary<String, Any>] = []
1755
+ var allRoutes: [[String: Any]] = []
1708
1756
  guard let _routes = routes else {
1709
1757
  return nil
1710
1758
  }
1711
1759
  for route in _routes {
1712
- let routeMetadata = serializeMetadata(
1713
- metadata: route.metadata
1714
- ) as! [String: Any]
1760
+ let routeMetadata =
1761
+ serializeMetadata(
1762
+ metadata: route.metadata
1763
+ ) as! [String: Any]
1715
1764
  let routeCLLocations = await getRouteLocations(
1716
1765
  store: store,
1717
1766
  route: route
1718
1767
  )
1719
1768
  let routeLocations = routeCLLocations.enumerated().map {
1720
- (i, loc) in serializeLocation(
1769
+ (i, loc) in
1770
+ serializeLocation(
1721
1771
  location: loc,
1722
- previousLocation: i == 0 ? nil: routeCLLocations[i - 1]
1772
+ previousLocation: i == 0 ? nil : routeCLLocations[i - 1]
1723
1773
  )
1724
1774
  }
1725
1775
  let routeInfos: [String: Any] = ["locations": routeLocations]
@@ -1731,59 +1781,60 @@ class ReactNativeHealthkit: RCTEventEmitter {
1731
1781
 
1732
1782
  @available(iOS 17.0.0, *)
1733
1783
  func getWorkoutPlan(workout: HKWorkout) async -> [String: Any]? {
1734
- #if canImport(WorkoutKit)
1735
- do {
1736
- let workoutPlan = try await workout.workoutPlan
1784
+ #if canImport(WorkoutKit)
1785
+ do {
1786
+ let workoutPlan = try await workout.workoutPlan
1737
1787
 
1738
- var dict = [String: Any]()
1739
- if (workoutPlan?.id) != nil {
1740
- dict["id"] = workoutPlan?.id.uuidString
1788
+ var dict = [String: Any]()
1789
+ if (workoutPlan?.id) != nil {
1790
+ dict["id"] = workoutPlan?.id.uuidString
1741
1791
 
1742
- }
1743
- if (workoutPlan?.workout.activity) != nil {
1744
- dict["activityType"] = workoutPlan?.workout.activity.rawValue
1745
- }
1792
+ }
1793
+ if (workoutPlan?.workout.activity) != nil {
1794
+ dict["activityType"] = workoutPlan?.workout.activity.rawValue
1795
+ }
1746
1796
 
1747
- if dict.isEmpty {
1797
+ if dict.isEmpty {
1798
+ return nil
1799
+ }
1800
+ return dict
1801
+ } catch {
1748
1802
  return nil
1749
1803
  }
1750
- return dict
1751
- } catch {
1804
+ #else
1752
1805
  return nil
1753
- }
1754
- #else
1755
- return nil
1756
- #endif
1806
+ #endif
1757
1807
  }
1758
1808
 
1759
1809
  @objc(getWorkoutPlanById:resolve:reject:)
1760
1810
  func getWorkoutPlanById(
1761
1811
  workoutUUID: String,
1762
1812
  resolve: @escaping RCTPromiseResolveBlock,
1763
- reject: @escaping RCTPromiseRejectBlock) {
1813
+ reject: @escaping RCTPromiseRejectBlock
1814
+ ) {
1764
1815
  if #available(iOS 17.0, *) {
1765
- #if canImport(WorkoutKit)
1766
- guard let store = _store else {
1767
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1768
- }
1816
+ #if canImport(WorkoutKit)
1817
+ guard let store = _store else {
1818
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1819
+ }
1769
1820
 
1770
- Task {
1771
- if let uuid = UUID(uuidString: workoutUUID) {
1772
- let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
1773
- if let workout {
1774
- let workoutPlan = await self.getWorkoutPlan(workout: workout)
1821
+ Task {
1822
+ if let uuid = UUID(uuidString: workoutUUID) {
1823
+ let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
1824
+ if let workout {
1825
+ let workoutPlan = await self.getWorkoutPlan(workout: workout)
1775
1826
 
1776
- return resolve(workoutPlan)
1827
+ return resolve(workoutPlan)
1828
+ } else {
1829
+ return reject(GENERIC_ERROR, "No workout found", nil)
1830
+ }
1777
1831
  } else {
1778
- return reject(GENERIC_ERROR, "No workout found", nil)
1832
+ return reject(GENERIC_ERROR, "Invalid UUID", nil)
1779
1833
  }
1780
- } else {
1781
- return reject(GENERIC_ERROR, "Invalid UUID", nil)
1782
1834
  }
1783
- }
1784
- #else
1785
- return resolve(nil)
1786
- #endif
1835
+ #else
1836
+ return resolve(nil)
1837
+ #endif
1787
1838
  } else {
1788
1839
  return resolve(nil)
1789
1840
  }
@@ -1814,25 +1865,26 @@ class ReactNativeHealthkit: RCTEventEmitter {
1814
1865
  func getWorkoutRoutes(
1815
1866
  workoutUUID: String,
1816
1867
  resolve: @escaping RCTPromiseResolveBlock,
1817
- reject: @escaping RCTPromiseRejectBlock) {
1868
+ reject: @escaping RCTPromiseRejectBlock
1869
+ ) {
1818
1870
 
1819
- guard let store = _store else {
1820
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1821
- }
1871
+ guard let store = _store else {
1872
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
1873
+ }
1822
1874
 
1823
- guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1824
- return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1825
- }
1875
+ guard let _workoutUUID = UUID(uuidString: workoutUUID) else {
1876
+ return reject("INVALID_UUID_ERROR", "Invalid UUID received", nil)
1877
+ }
1826
1878
 
1827
- Task {
1828
- do {
1829
- let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1830
- resolve(locations)
1831
- } catch {
1832
- reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1833
- }
1879
+ Task {
1880
+ do {
1881
+ let locations = await getSerializedWorkoutLocations(store: store, workoutUUID: _workoutUUID)
1882
+ resolve(locations)
1883
+ } catch {
1884
+ reject("WORKOUT_LOCATION_ERROR", "Failed to retrieve workout locations", nil)
1834
1885
  }
1835
1886
  }
1887
+ }
1836
1888
 
1837
1889
  typealias HKAnchoredObjectQueryResult = (
1838
1890
  samples: [HKSample],
@@ -1854,13 +1906,14 @@ class ReactNativeHealthkit: RCTEventEmitter {
1854
1906
  predicate: predicate,
1855
1907
  anchor: anchor,
1856
1908
  limit: limit
1857
- ) { (
1858
- _: HKAnchoredObjectQuery,
1859
- s: [HKSample]?,
1860
- deletedSamples: [HKDeletedObject]?,
1861
- newAnchor: HKQueryAnchor?,
1862
- error: Error?
1863
- ) in
1909
+ ) {
1910
+ (
1911
+ _: HKAnchoredObjectQuery,
1912
+ s: [HKSample]?,
1913
+ deletedSamples: [HKDeletedObject]?,
1914
+ newAnchor: HKQueryAnchor?,
1915
+ error: Error?
1916
+ ) in
1864
1917
  if let err = error {
1865
1918
  return continuation.resume(throwing: err)
1866
1919
  } else {
@@ -1887,18 +1940,19 @@ class ReactNativeHealthkit: RCTEventEmitter {
1887
1940
  func getHeartbeatSeriesHeartbeats(
1888
1941
  store: HKHealthStore,
1889
1942
  sample: HKHeartbeatSeriesSample
1890
- ) async throws -> [Dictionary<String, Any>] {
1943
+ ) async throws -> [[String: Any]] {
1891
1944
  let beatTimes = try await withCheckedThrowingContinuation {
1892
- (continuation: CheckedContinuation<[Dictionary<String, Any>], Error>) in
1893
- var allBeats: [Dictionary<String, Any>] = []
1894
-
1895
- let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) { (
1896
- _: HKHeartbeatSeriesQuery,
1897
- timeSinceSeriesStart: TimeInterval,
1898
- precededByGap: Bool,
1899
- done: Bool,
1900
- error: Error?
1901
- ) in
1945
+ (continuation: CheckedContinuation<[[String: Any]], Error>) in
1946
+ var allBeats: [[String: Any]] = []
1947
+
1948
+ let query = HKHeartbeatSeriesQuery(heartbeatSeries: sample) {
1949
+ (
1950
+ _: HKHeartbeatSeriesQuery,
1951
+ timeSinceSeriesStart: TimeInterval,
1952
+ precededByGap: Bool,
1953
+ done: Bool,
1954
+ error: Error?
1955
+ ) in
1902
1956
  if let err = error {
1903
1957
  return continuation.resume(throwing: err)
1904
1958
  } else {
@@ -1975,7 +2029,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
1975
2029
  anchor: actualAnchor
1976
2030
  )
1977
2031
 
1978
- var allHeartbeatSamples: [Dictionary<String, Any>] = []
2032
+ var allHeartbeatSamples: [[String: Any]] = []
1979
2033
  for sample in queryResult.samples as! [HKHeartbeatSeriesSample] {
1980
2034
  allHeartbeatSamples.append(
1981
2035
  try await getSerializedHeartbeatSeriesSample(
@@ -2012,7 +2066,9 @@ class ReactNativeHealthkit: RCTEventEmitter {
2012
2066
  sampleType: HKSeriesType.heartbeat(),
2013
2067
  predicate: predicate,
2014
2068
  limit: limit,
2015
- sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]
2069
+ sortDescriptors: [
2070
+ NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
2071
+ ]
2016
2072
  ) { (_: HKSampleQuery, sample: [HKSample]?, error: Error?) in
2017
2073
  if let err = error {
2018
2074
  continuation.resume(throwing: err)
@@ -2065,7 +2121,7 @@ class ReactNativeHealthkit: RCTEventEmitter {
2065
2121
  ascending: ascending
2066
2122
  )
2067
2123
 
2068
- var allHeartbeatSamples: [Dictionary<String, Any>] = []
2124
+ var allHeartbeatSamples: [[String: Any]] = []
2069
2125
  for sample in samples as! [HKHeartbeatSeriesSample] {
2070
2126
  allHeartbeatSamples.append(
2071
2127
  try await getSerializedHeartbeatSeriesSample(
@@ -2105,7 +2161,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
2105
2161
  }
2106
2162
  }
2107
2163
 
2108
- @available(iOS 18.0, *)
2109
2164
  @objc(queryStateOfMindSamples:to:limit:ascending:resolve:reject:)
2110
2165
  func queryStateOfMindSamples(
2111
2166
  from: Date,
@@ -2115,56 +2170,60 @@ class ReactNativeHealthkit: RCTEventEmitter {
2115
2170
  resolve: @escaping RCTPromiseResolveBlock,
2116
2171
  reject: @escaping RCTPromiseRejectBlock
2117
2172
  ) {
2118
- guard let store = _store else {
2119
- return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
2120
- }
2121
-
2122
- Task {
2123
- let from = dateOrNilIfZero(date: from)
2124
- let to = dateOrNilIfZero(date: to)
2125
-
2126
- let predicate = createPredicate(
2127
- from: from,
2128
- to: to
2129
- )
2130
-
2131
- let limit = limitOrNilIfZero(
2132
- limit: limit
2133
- )
2173
+ #if compiler(>=6)
2174
+ if #available(iOS 18.0, *) {
2175
+ guard let store = _store else {
2176
+ return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
2177
+ }
2134
2178
 
2135
- let type = HKStateOfMindType.stateOfMindType()
2179
+ Task {
2180
+ let from = dateOrNilIfZero(date: from)
2181
+ let to = dateOrNilIfZero(date: to)
2136
2182
 
2137
- let query = HKSampleQuery(
2138
- sampleType: type,
2139
- predicate: predicate,
2140
- limit: limit,
2141
- sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)]
2142
- ) { (_, samples, error) in
2143
- if let error = error {
2144
- reject(GENERIC_ERROR, error.localizedDescription, error)
2145
- return
2146
- }
2183
+ let predicate = createPredicate(
2184
+ from: from,
2185
+ to: to
2186
+ )
2147
2187
 
2148
- guard let samples = samples as? [HKStateOfMind] else {
2149
- resolve([])
2150
- return
2151
- }
2188
+ let limit = limitOrNilIfZero(
2189
+ limit: limit
2190
+ )
2152
2191
 
2153
- let serializedSamples = samples.map { sample in
2154
- var associations: [Int] = []
2155
- if #available(iOS 18.0, *) {
2156
- associations = sample.associations.map({ association in
2157
- return association.rawValue
2158
- })
2192
+ let type = HKStateOfMindType.stateOfMindType()
2193
+
2194
+ let query = HKSampleQuery(
2195
+ sampleType: type,
2196
+ predicate: predicate,
2197
+ limit: limit,
2198
+ sortDescriptors: [
2199
+ NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
2200
+ ]
2201
+ ) { (_, samples, error) in
2202
+ if let error = error {
2203
+ reject(GENERIC_ERROR, error.localizedDescription, error)
2204
+ return
2159
2205
  }
2160
2206
 
2161
- var labels: [Int] = []
2162
- if #available(iOS 18.0, *) {
2163
- labels = sample.labels.map({ label in
2164
- return label.rawValue
2165
- })
2207
+ guard let samples = samples as? [HKStateOfMind] else {
2208
+ resolve([])
2209
+ return
2166
2210
  }
2167
2211
 
2212
+ let serializedSamples = samples.map { sample in
2213
+ var associations: [Int] = []
2214
+ if #available(iOS 18.0, *) {
2215
+ associations = sample.associations.map({ association in
2216
+ return association.rawValue
2217
+ })
2218
+ }
2219
+
2220
+ var labels: [Int] = []
2221
+ if #available(iOS 18.0, *) {
2222
+ labels = sample.labels.map({ label in
2223
+ return label.rawValue
2224
+ })
2225
+ }
2226
+
2168
2227
  return [
2169
2228
  "uuid": sample.uuid.uuidString,
2170
2229
  "device": serializeDevice(_device: sample.device) as Any,
@@ -2176,15 +2235,22 @@ class ReactNativeHealthkit: RCTEventEmitter {
2176
2235
  "associations": associations,
2177
2236
  "labels": labels,
2178
2237
  "metadata": serializeMetadata(metadata: sample.metadata),
2179
- "sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision) as Any
2238
+ "sourceRevision": serializeSourceRevision(_sourceRevision: sample.sourceRevision)
2239
+ as Any
2180
2240
  ]
2241
+ }
2242
+
2243
+ resolve(serializedSamples)
2181
2244
  }
2182
2245
 
2183
- resolve(serializedSamples)
2246
+ store.execute(query)
2184
2247
  }
2185
-
2186
- store.execute(query)
2187
- }
2248
+ } else {
2249
+ reject("STATE_OF_MIND_ERROR", "State of Mind features require iOS 18.0 or later", nil)
2250
+ }
2251
+ #else
2252
+ reject(
2253
+ "STATE_OF_MIND_ERROR", "State of Mind features require Xcode 16 or later to compile", nil)
2254
+ #endif
2188
2255
  }
2189
-
2190
2256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kingstinct/react-native-healthkit",
3
- "version": "8.4.0",
3
+ "version": "8.5.0",
4
4
  "description": "React Native bindings for HealthKit",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",