Skip to content

Commit 1679eb1

Browse files
committed
Updates for HealthKit decoupling changes in LoopKit
1 parent fee85e0 commit 1679eb1

File tree

10 files changed

+79
-110
lines changed

10 files changed

+79
-110
lines changed

Loop Status Extension/StatusViewController.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,12 @@ class StatusViewController: UIViewController, NCWidgetProviding {
8484
expireAfter: localCacheDuration)
8585

8686
lazy var glucoseStore = GlucoseStore(
87-
healthStore: healthStore,
88-
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps,
89-
storeSamplesToHealthKit: false,
9087
cacheStore: cacheStore,
91-
observationEnabled: false,
9288
provenanceIdentifier: HKSource.default().bundleIdentifier
9389
)
9490

9591
lazy var doseStore = DoseStore(
96-
healthStore: healthStore,
97-
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitDoseSamplesFromOtherApps,
98-
storeSamplesToHealthKit: false,
9992
cacheStore: cacheStore,
100-
observationEnabled: false,
10193
insulinModelProvider: PresetInsulinModelProvider(defaultRapidActingModel: settingsStore.latestSettings?.defaultRapidActingModel?.presetForRapidActingInsulin),
10294
longestEffectDuration: ExponentialInsulinModelPreset.rapidActingAdult.effectDuration,
10395
basalProfile: settingsStore.latestSettings?.basalRateSchedule,

Loop/Extensions/DeviceDataManager+BolusEntryViewModelDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extension DeviceDataManager: BolusEntryViewModelDelegate, ManualDoseViewModelDel
7070
}
7171

7272
var preferredGlucoseUnit: HKUnit {
73-
return glucoseStore.preferredUnit ?? .milligramsPerDeciliter
73+
return displayGlucoseUnitObservable.displayGlucoseUnit
7474
}
7575

7676
var pumpInsulinType: InsulinType? {

Loop/Managers/DeviceDataManager.swift

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,13 @@ final class DeviceDataManager {
163163
var readTypes: Set<HKSampleType> = []
164164

165165
if FeatureFlags.observeHealthKitCarbSamplesFromOtherApps {
166-
readTypes.insert(carbStore.sampleType)
166+
readTypes.insert(HealthKitSampleStore.carbType)
167167
}
168168
if FeatureFlags.observeHealthKitDoseSamplesFromOtherApps {
169-
readTypes.insert(doseStore.sampleType)
169+
readTypes.insert(HealthKitSampleStore.insulinQuantityType)
170170
}
171171
if FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps {
172-
readTypes.insert(glucoseStore.sampleType)
172+
readTypes.insert(HealthKitSampleStore.glucoseType)
173173
}
174174

175175
readTypes.insert(HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!)
@@ -180,35 +180,35 @@ final class DeviceDataManager {
180180
/// All the HealthKit types to be shared by stores
181181
private var shareTypes: Set<HKSampleType> {
182182
return Set([
183-
glucoseStore.sampleType,
184-
carbStore.sampleType,
185-
doseStore.sampleType,
183+
HealthKitSampleStore.glucoseType,
184+
HealthKitSampleStore.carbType,
185+
HealthKitSampleStore.insulinQuantityType,
186186
])
187187
}
188188

189189
var sleepDataAuthorizationRequired: Bool {
190-
return carbStore.healthStore.authorizationStatus(for: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!) == .notDetermined
190+
return healthStore.authorizationStatus(for: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!) == .notDetermined
191191
}
192192

193193
var sleepDataSharingDenied: Bool {
194-
return carbStore.healthStore.authorizationStatus(for: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!) == .sharingDenied
194+
return healthStore.authorizationStatus(for: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!) == .sharingDenied
195195
}
196196

197197
/// True if any stores require HealthKit authorization
198198
var authorizationRequired: Bool {
199-
return glucoseStore.authorizationRequired ||
200-
carbStore.authorizationRequired ||
201-
doseStore.authorizationRequired ||
202-
sleepDataAuthorizationRequired
199+
return healthStore.authorizationStatus(for: HealthKitSampleStore.glucoseType) == .notDetermined ||
200+
healthStore.authorizationStatus(for: HealthKitSampleStore.carbType) == .notDetermined ||
201+
healthStore.authorizationStatus(for: HealthKitSampleStore.insulinQuantityType) == .notDetermined ||
202+
sleepDataAuthorizationRequired
203203
}
204204

205205
/// True if the user has explicitly denied access to any stores' HealthKit types
206-
private var sharingDenied: Bool {
207-
return glucoseStore.sharingDenied ||
208-
carbStore.sharingDenied ||
209-
doseStore.sharingDenied ||
210-
sleepDataSharingDenied
211-
}
206+
// private var sharingDenied: Bool {
207+
// return healthStore.authorizationStatus(for: HealthKitSampleStore.glucoseType) == .sharingDenied ||
208+
// carbStore.sharingDenied ||
209+
// doseStore.sharingDenied ||
210+
// sleepDataSharingDenied
211+
// }
212212

213213
// MARK: Services
214214

@@ -277,14 +277,19 @@ final class DeviceDataManager {
277277

278278
let absorptionTimes = LoopCoreConstants.defaultCarbAbsorptionTimes
279279
let sensitivitySchedule = settingsManager.latestSettings.insulinSensitivitySchedule
280-
281-
self.carbStore = CarbStore(
280+
281+
let carbHealthStore = HealthKitSampleStore(
282282
healthStore: healthStore,
283283
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitCarbSamplesFromOtherApps, // At some point we should let the user decide which apps they would like to import from.
284+
type: HealthKitSampleStore.carbType,
285+
observationStart: Date().addingTimeInterval(-absorptionTimes.slow * 2)
286+
)
287+
288+
self.carbStore = CarbStore(
289+
healthKitSampleStore: carbHealthStore,
284290
cacheStore: cacheStore,
285291
cacheLength: localCacheDuration,
286292
defaultAbsorptionTimes: absorptionTimes,
287-
observationInterval: absorptionTimes.slow * 2,
288293
carbRatioSchedule: settingsManager.latestSettings.carbRatioSchedule,
289294
insulinSensitivitySchedule: sensitivitySchedule,
290295
overrideHistory: overrideHistory,
@@ -300,10 +305,16 @@ final class DeviceDataManager {
300305
}
301306

302307
self.analyticsServicesManager = analyticsServicesManager
303-
304-
self.doseStore = DoseStore(
308+
309+
let insulinHealthStore = HealthKitSampleStore(
305310
healthStore: healthStore,
306311
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitDoseSamplesFromOtherApps,
312+
type: HealthKitSampleStore.insulinQuantityType,
313+
observationStart: Date().addingTimeInterval(-absorptionTimes.slow * 2)
314+
)
315+
316+
self.doseStore = DoseStore(
317+
healthKitSampleStore: insulinHealthStore,
307318
cacheStore: cacheStore,
308319
cacheLength: localCacheDuration,
309320
insulinModelProvider: insulinModelProvider,
@@ -314,13 +325,18 @@ final class DeviceDataManager {
314325
lastPumpEventsReconciliation: nil, // PumpManager is nil at this point. Will update this via addPumpEvents below
315326
provenanceIdentifier: HKSource.default().bundleIdentifier
316327
)
328+
329+
let glucoseHealthStore = HealthKitSampleStore(
330+
healthStore: healthStore,
331+
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps,
332+
type: HealthKitSampleStore.glucoseType,
333+
observationStart: Date().addingTimeInterval(-.hours(24))
334+
)
317335

318336
self.glucoseStore = GlucoseStore(
319-
healthStore: healthStore,
320-
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps,
337+
healthKitSampleStore: glucoseHealthStore,
321338
cacheStore: cacheStore,
322339
cacheLength: localCacheDuration,
323-
observationInterval: .hours(24),
324340
provenanceIdentifier: HKSource.default().bundleIdentifier
325341
)
326342

@@ -334,7 +350,7 @@ final class DeviceDataManager {
334350
self.automaticDosingStatus = automaticDosingStatus
335351

336352
// HealthStorePreferredGlucoseUnitDidChange will be notified once the user completes the health access form. Set to .milligramsPerDeciliter until then
337-
displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: glucoseStore.preferredUnit ?? .milligramsPerDeciliter)
353+
displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .milligramsPerDeciliter)
338354

339355
self.trustedTimeChecker = trustedTimeChecker
340356

@@ -438,14 +454,16 @@ final class DeviceDataManager {
438454
.assign(to: \.automaticDosingStatus.isAutomaticDosingAllowed, on: self)
439455
.store(in: &cancellables)
440456

441-
NotificationCenter.default.addObserver(forName: .HealthStorePreferredGlucoseUnitDidChange, object: glucoseStore.healthStore, queue: nil) { [weak self] _ in
442-
guard let strongSelf = self else {
457+
NotificationCenter.default.addObserver(forName: .HealthStorePreferredGlucoseUnitDidChange, object: healthStore, queue: nil) { [weak self] _ in
458+
guard let self else {
443459
return
444460
}
445461

446-
if let preferredGlucoseUnit = strongSelf.glucoseStore.preferredUnit {
447-
strongSelf.displayGlucoseUnitObservable.displayGlucoseUnitDidChange(to: preferredGlucoseUnit)
448-
strongSelf.notifyObserversOfDisplayGlucoseUnitChange(to: preferredGlucoseUnit)
462+
Task {
463+
if let unit = await self.healthStore.cachedPreferredUnits(for: .bloodGlucose) {
464+
self.displayGlucoseUnitObservable.displayGlucoseUnitDidChange(to: unit)
465+
self.notifyObserversOfDisplayGlucoseUnitChange(to: unit)
466+
}
449467
}
450468
}
451469
}
@@ -644,9 +662,9 @@ final class DeviceDataManager {
644662
healthStore.requestAuthorization(toShare: shareTypes, read: readTypes) { (success, error) in
645663
if success {
646664
// Call the individual authorization methods to trigger query creation
647-
self.carbStore.authorize(toShare: true, read: FeatureFlags.observeHealthKitCarbSamplesFromOtherApps, { _ in })
648-
self.doseStore.insulinDeliveryStore.authorize(toShare: true, read: FeatureFlags.observeHealthKitDoseSamplesFromOtherApps, { _ in })
649-
self.glucoseStore.authorize(toShare: true, read: FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps, { _ in })
665+
self.carbStore.hkSampleStore?.authorizationIsDetermined()
666+
self.doseStore.hkSampleStore?.authorizationIsDetermined()
667+
self.glucoseStore.hkSampleStore?.authorizationIsDetermined()
650668
}
651669

652670
self.getHealthStoreAuthorization(completion)
@@ -1212,7 +1230,8 @@ extension DeviceDataManager {
12121230
return
12131231
}
12141232

1215-
guard !self.doseStore.sharingDenied else {
1233+
let insulinSharingDenied = self.healthStore.authorizationStatus(for: HealthKitSampleStore.insulinQuantityType) == .sharingDenied
1234+
guard !insulinSharingDenied else {
12161235
// only clear cache since access to health kit is denied
12171236
insulinDeliveryStore.purgeCachedInsulinDeliveryObjects() { error in
12181237
completion?(error)
@@ -1232,7 +1251,8 @@ extension DeviceDataManager {
12321251
return
12331252
}
12341253

1235-
guard !glucoseStore.sharingDenied else {
1254+
let glucoseSharingDenied = self.healthStore.authorizationStatus(for: HealthKitSampleStore.glucoseType) == .sharingDenied
1255+
guard !glucoseSharingDenied else {
12361256
// only clear cache since access to health kit is denied
12371257
glucoseStore.purgeCachedGlucoseObjects() { error in
12381258
completion?(error)
@@ -1673,10 +1693,8 @@ extension DeviceDataManager {
16731693
func addDisplayGlucoseUnitObserver(_ observer: DisplayGlucoseUnitObserver) {
16741694
let queue = DispatchQueue.main
16751695
displayGlucoseUnitObservers.insert(observer, queue: queue)
1676-
if let displayGlucoseUnit = glucoseStore.preferredUnit {
1677-
queue.async {
1678-
observer.displayGlucoseUnitDidChange(to: displayGlucoseUnit)
1679-
}
1696+
queue.async {
1697+
observer.displayGlucoseUnitDidChange(to: self.displayGlucoseUnitObservable.displayGlucoseUnit)
16801698
}
16811699
}
16821700

Loop/Managers/ExtensionDataManager.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,7 @@ final class ExtensionDataManager {
6161
}
6262

6363
private func update() {
64-
guard let unit = (deviceManager.glucoseStore.preferredUnit ?? ExtensionDataManager.context?.predictedGlucose?.unit) else {
65-
return
66-
}
67-
68-
createStatusContext(glucoseUnit: unit) { (context) in
64+
createStatusContext(glucoseUnit: deviceManager.preferredGlucoseUnit) { (context) in
6965
if let context = context {
7066
ExtensionDataManager.context = context
7167
}

Loop/Managers/Store Protocols/CarbStoreProtocol.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import HealthKit
1111

1212
protocol CarbStoreProtocol: AnyObject {
1313

14-
var sampleType: HKSampleType { get }
15-
1614
var preferredUnit: HKUnit! { get }
1715

1816
var delegate: CarbStoreDelegate? { get set }
@@ -32,13 +30,6 @@ protocol CarbStoreProtocol: AnyObject {
3230

3331
var defaultAbsorptionTimes: CarbStore.DefaultAbsorptionTimes { get }
3432

35-
// MARK: HealthKit
36-
var authorizationRequired: Bool { get }
37-
38-
var sharingDenied: Bool { get }
39-
40-
func authorize(toShare: Bool, read: Bool, _ completion: @escaping (_ result: HealthKitSampleStoreResult<Bool>) -> Void)
41-
4233
// MARK: Data Management
4334
func replaceCarbEntry(_ oldEntry: StoredCarbEntry, withEntry newEntry: NewCarbEntry, completion: @escaping (_ result: CarbStoreResult<StoredCarbEntry>) -> Void)
4435

Loop/Managers/Store Protocols/DoseStoreProtocol.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,11 @@ protocol DoseStoreProtocol: AnyObject {
2121

2222
var basalProfileApplyingOverrideHistory: BasalRateSchedule? { get }
2323

24-
// MARK: authorization
25-
var authorizationRequired: Bool { get }
26-
27-
var sharingDenied: Bool { get }
28-
2924
// MARK: store information
3025
var lastReservoirValue: LoopKit.ReservoirValue? { get }
3126

3227
var lastAddedPumpData: Date { get }
3328

34-
var sampleType: HKSampleType { get }
35-
3629
var delegate: DoseStoreDelegate? { get set }
3730

3831
var device: HKDevice? { get set }

Loop/Managers/Store Protocols/GlucoseStoreProtocol.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,10 @@ protocol GlucoseStoreProtocol: AnyObject {
1313

1414
var latestGlucose: GlucoseSampleValue? { get }
1515

16-
var preferredUnit: HKUnit? { get }
17-
18-
var sampleType: HKSampleType { get }
19-
2016
var delegate: GlucoseStoreDelegate? { get set }
2117

2218
var managedDataInterval: TimeInterval? { get set }
2319

24-
// MARK: HealthKit
25-
var authorizationRequired: Bool { get }
26-
27-
var sharingDenied: Bool { get }
28-
29-
func authorize(toShare: Bool, read: Bool, _ completion: @escaping (_ result: HealthKitSampleStoreResult<Bool>) -> Void)
30-
3120
// MARK: Sample Management
3221
func addGlucoseSamples(_ samples: [NewGlucoseSample], completion: @escaping (_ result: Result<[StoredGlucoseSample], Error>) -> Void)
3322

Loop/Managers/WatchDataManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ final class WatchDataManager: NSObject {
245245

246246
let carbsOnBoard = state.carbsOnBoard
247247

248-
let context = WatchContext(glucose: glucose, glucoseUnit: self.deviceManager.glucoseStore.preferredUnit)
248+
let context = WatchContext(glucose: glucose, glucoseUnit: self.deviceManager.preferredGlucoseUnit)
249249
context.reservoir = reservoir?.unitVolume
250250
context.loopLastRunDate = manager.lastLoopCompleted
251251
context.cob = carbsOnBoard?.quantity.doubleValue(for: HKUnit.gram())

StatusWidget/StatusWidget.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ class StatusWidgetProvider: TimelineProvider {
3131
expireAfter: localCacheDuration)
3232

3333
lazy var glucoseStore = GlucoseStore(
34-
healthStore: healthStore,
35-
observeHealthKitSamplesFromOtherApps: FeatureFlags.observeHealthKitGlucoseSamplesFromOtherApps,
36-
storeSamplesToHealthKit: false,
3734
cacheStore: cacheStore,
38-
observationEnabled: false,
3935
provenanceIdentifier: HKSource.default().bundleIdentifier
4036
)
4137

@@ -112,26 +108,30 @@ class StatusWidgetProvider: TimelineProvider {
112108
}
113109
group.leave()
114110
}
115-
group.notify(queue: .main) {
111+
group.wait()
112+
113+
let finalGlucose = glucose
114+
115+
Task { @MainActor in
116116
guard let defaults = self.defaults,
117117
let context = defaults.statusExtensionContext,
118118
let contextUpdatedAt = context.createdAt,
119-
let unit = self.glucoseStore.preferredUnit
119+
let unit = await healthStore.cachedPreferredUnits(for: .bloodGlucose)
120120
else {
121121
return
122122
}
123-
123+
124124
let lastCompleted = context.lastLoopCompleted
125-
125+
126126
let closeLoop = context.isClosedLoop ?? false
127-
127+
128128
let netBasal = context.netBasal
129-
130-
let currentGlucose = glucose.last
129+
130+
let currentGlucose = finalGlucose.last
131131
var previousGlucose: GlucoseValue?
132132

133-
if glucose.count > 1 {
134-
previousGlucose = glucose[glucose.count - 2]
133+
if finalGlucose.count > 1 {
134+
previousGlucose = finalGlucose[finalGlucose.count - 2]
135135
}
136136

137137
var delta: HKQuantity?
@@ -146,7 +146,7 @@ class StatusWidgetProvider: TimelineProvider {
146146
}
147147

148148
let predictedGlucose = context.predictedGlucose?.samples
149-
149+
150150
let eventualGlucose = predictedGlucose?.last
151151

152152
let updateDate = Date()

0 commit comments

Comments
 (0)