Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query Property Wrappers, HealthChart, and Other Refactoring #27

Merged
merged 96 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
9aaaa3a
[WIP] query property wrappers, concepts of a HealthChart
lukaskollmer Jan 12, 2025
d84d450
little cleanup
lukaskollmer Jan 12, 2025
af2254f
fix typo
lukaskollmer Jan 14, 2025
2fcf1b2
rework the HealthKit module itself a little bit
lukaskollmer Jan 14, 2025
335f426
small fixes, improve documentation
lukaskollmer Jan 14, 2025
9d6be14
centralise HealthKit auth handling, delay starting background observe…
lukaskollmer Jan 15, 2025
bea1710
make the config component configure function async, rework auth handl…
lukaskollmer Jan 15, 2025
4bb52de
RIP HealthKitTests (they were just checking the UserDefaults auth stu…
lukaskollmer Jan 15, 2025
cacfbe6
rename `__HKSampleTypeProviding` to `_HKSampleWithSampleType`
lukaskollmer Jan 16, 2025
fac771e
add HealthKit. isAuthorized, rename some parameters
lukaskollmer Jan 18, 2025
79e2e3d
merge SpeziHealthKitUI into SpeziHealthKit
lukaskollmer Jan 18, 2025
f8d16b4
enable "ExistentialAny" upcoming feature flag
lukaskollmer Jan 18, 2025
b642d79
rename HealthKitDataAccessRequirements → HealthKit.DataAccessRequirem…
lukaskollmer Jan 19, 2025
b47b1db
remove some things
lukaskollmer Jan 19, 2025
2cfdffe
remov
lukaskollmer Jan 19, 2025
80d3bbb
rename HealthKitSampleType → SampleType
lukaskollmer Jan 19, 2025
b246334
inline all the sample type getters
lukaskollmer Jan 19, 2025
edeed71
mark most of SampleType as @inlinable, localize sample type names and…
lukaskollmer Jan 19, 2025
ee4d321
try restore proper dependencies
lukaskollmer Jan 19, 2025
0ca7d74
add reference snapshots
lukaskollmer Jan 19, 2025
2b712de
add macOS support
lukaskollmer Jan 20, 2025
5f83cff
rework the "HealthKitDataSource" API
lukaskollmer Jan 20, 2025
2c0a599
split up the HealthChart into multiple files
lukaskollmer Jan 20, 2025
9e4e6b7
remove `frequency` parameter from `enableBackgroundDelivery` function
lukaskollmer Jan 20, 2025
6459e46
rework documentation (pt01)
lukaskollmer Jan 21, 2025
726c4ee
x
lukaskollmer Jan 21, 2025
3acef8a
remove InteractiveHealthChart
lukaskollmer Jan 21, 2025
3469baa
more documentation rework
lukaskollmer Jan 21, 2025
ab0e4ca
fix docc copyright notices
lukaskollmer Jan 21, 2025
8fab017
remove `withTimeRange` function
lukaskollmer Jan 21, 2025
e8ea0d0
HealthChart: remove errors overlay
lukaskollmer Jan 21, 2025
1aa6201
HealthChart: remove interactivity, swiftlint
lukaskollmer Jan 21, 2025
90ba660
rework HealthKitQueryTimeRange
lukaskollmer Jan 21, 2025
cb9fcf1
fix UITest dependencies
lukaskollmer Jan 21, 2025
d6aef67
adjust tests
lukaskollmer Jan 21, 2025
7dddd47
[Tests] force en_US locale
lukaskollmer Jan 21, 2025
264c1c7
HealthKitCharacteristicQuery : include usage example in docs
lukaskollmer Jan 21, 2025
2675017
bring DocC changes to readme
lukaskollmer Jan 21, 2025
e18f89c
vanity commit
lukaskollmer Jan 21, 2025
89c3407
UITests: enable swift 6 language mode
lukaskollmer Jan 22, 2025
d1341a5
[tests] always force use of en_US locale and time zone
lukaskollmer Jan 22, 2025
d9dabb8
rename `.anchorQuery` to `.continuous`
lukaskollmer Jan 22, 2025
3fc64ea
hmmm
lukaskollmer Jan 22, 2025
fb4df52
x
lukaskollmer Jan 22, 2025
494b62f
hmmm
lukaskollmer Jan 22, 2025
f5ad400
fix HealthKitQuery
lukaskollmer Jan 22, 2025
0621ef2
try to add some more tests
lukaskollmer Jan 22, 2025
1e6f32d
oof
lukaskollmer Jan 22, 2025
09eb23e
uwuuu
lukaskollmer Jan 22, 2025
282b12c
try to fix the test
lukaskollmer Jan 23, 2025
587b384
swiftlint
lukaskollmer Jan 23, 2025
b320564
my bad
lukaskollmer Jan 23, 2025
2a561b5
try to fix the test not being able to query app.staticTexts
lukaskollmer Jan 23, 2025
82357bd
license?
lukaskollmer Jan 23, 2025
268ae4c
license.
lukaskollmer Jan 23, 2025
5a8525e
include statistic query chart in UI tests; simplify SampleType and He…
lukaskollmer Jan 23, 2025
c322775
RequestReadAccess: add Info.plist notice
lukaskollmer Jan 23, 2025
81bc82f
de-generify the HealthChart
lukaskollmer Jan 23, 2025
9e318de
add `HealthKitQueryResults.isCurrentlyPerformingInitialFetch`
lukaskollmer Jan 23, 2025
8e0da6b
SampleType.heartRate: adjust expectedValuesRange
lukaskollmer Jan 23, 2025
81ded84
x
lukaskollmer Jan 23, 2025
a90c6bc
fix operator definition
lukaskollmer Jan 23, 2025
17a4b1d
test updates
lukaskollmer Jan 23, 2025
8796421
update XCTHealthKit dependency
lukaskollmer Jan 23, 2025
352ead0
Update SpeziHealthKitTests.swift
lukaskollmer Jan 23, 2025
093c8a4
rework CollectSample API
lukaskollmer Jan 24, 2025
3ca0c92
hmmm
lukaskollmer Jan 25, 2025
eb26de2
move HealthChart back into a separate UI target
lukaskollmer Jan 25, 2025
4b2e989
try to update docs
lukaskollmer Jan 25, 2025
14b6426
swiftlint
lukaskollmer Jan 25, 2025
b1a2cf6
remove stale localizations
lukaskollmer Jan 25, 2025
23ebe21
add well-known sample types
lukaskollmer Jan 25, 2025
f56fc66
codecov.yml licence
lukaskollmer Jan 25, 2025
8bf0d9b
add back the expectedValueRanges; fix tests
lukaskollmer Jan 25, 2025
3901f98
add missing licenses
lukaskollmer Jan 25, 2025
6e8e0bd
fix ui tests?
lukaskollmer Jan 25, 2025
b88bd4a
try to fix the test running
lukaskollmer Jan 25, 2025
3081291
try to fix ui tests
lukaskollmer Jan 25, 2025
bf3bef1
???
lukaskollmer Jan 25, 2025
3dcde39
codecov.yml
lukaskollmer Jan 25, 2025
3b82abb
codecov.yml
lukaskollmer Jan 25, 2025
60409a8
build-and-test.yml
lukaskollmer Jan 25, 2025
9fcd070
codecov.yml
lukaskollmer Jan 25, 2025
f5573cc
improve typing (use SampleType in more cases), improve tests
lukaskollmer Jan 25, 2025
b9d85f0
swiftlint
lukaskollmer Jan 25, 2025
541266c
add tests for HealthKitCharacteristic, rework it a little bit
lukaskollmer Jan 26, 2025
d82bfd7
try to fix ui tests
lukaskollmer Jan 26, 2025
390ecb6
try to fix ui tests
lukaskollmer Jan 26, 2025
6e75e9d
characteristic query tests
lukaskollmer Jan 27, 2025
463d289
try to fix ui tests
lukaskollmer Jan 27, 2025
e32a576
fix ui tests
lukaskollmer Jan 27, 2025
5c817ac
.spi.yml
lukaskollmer Jan 27, 2025
e9f513a
remove the remaining (now-unused) UserDefaults code
lukaskollmer Jan 27, 2025
cea84f0
update CollectSample docs to reflect new situation
lukaskollmer Jan 27, 2025
6a5d4d1
switch XCTHealthKit dependency
lukaskollmer Jan 27, 2025
f3d59b1
XCTHealthKit update
lukaskollmer Jan 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ let package = Package(
.library(name: "SpeziHealthKit", targets: ["SpeziHealthKit"])
],
dependencies: [
.package(path: "../Spezi"),
.package(path: "../SpeziFoundation"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.17.7")
.package(url: "https://github.com/StanfordSpezi/Spezi.git", from: "1.8.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation.git", from: "2.1.0"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.7")
] + swiftLintPackage(),
targets: [
.target(
Expand Down
12 changes: 6 additions & 6 deletions Sources/SpeziHealthKit/CollectSample/CollectSample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import HealthKit
import Spezi


/// Collects a specified ``HealthKitSampleType`` via the ``HealthKit`` module.
/// Collects a specified ``SampleType`` via the ``HealthKit`` module.
///
/// This structure define what and how the ``HealthKit`` samples are collected. By default, all samples of the provided ``HealthKitSampleType`` will be collected.
/// This structure define what and how the ``HealthKit`` samples are collected. By default, all samples of the provided ``SampleType`` will be collected.
/// The collection starts on calling ``HealthKit/triggerDataSourceCollection()`` if you configure the `deliverySetting` as ``HealthKitDeliverySetting/manual(saveAnchor:)`` or automatic once the application is launched when you configure anything else than manual, i.e. ``HealthKitDeliverySetting/anchorQuery(_:saveAnchor:)`` or ``HealthKitDeliverySetting/background(_:saveAnchor:)``.
///
/// Your can filter the HealthKit samples to collect via an `NSPredicate`.
Expand Down Expand Up @@ -51,19 +51,19 @@ public struct CollectSample: HealthKitConfigurationComponent {
private let predicate: NSPredicate?
private let deliverySetting: HealthKitDeliverySetting

public var dataAccessRequirements: HealthKitDataAccessRequirements {
public var dataAccessRequirements: HealthKit.DataAccessRequirements {
.init(read: [sampleType])
}


/// - Parameters:
/// - sampleType: The ``HealthKitSampleType`` that should be collected
/// - sampleType: The ``SampleType`` that should be collected
/// - predicate: A custom predicate that should be passed to the HealthKit query.
/// The default predicate collects all samples that have been collected from the first time that the user
/// provided the application authorization to collect the samples.
/// - deliverySetting: The ``HealthKitDeliverySetting`` that should be used to collect the sample type. `.manual` is the default argument used.
public init(
_ sampleType: HealthKitSampleType<some Any>,
_ sampleType: SampleType<some Any>,
predicate: NSPredicate? = nil,
delivery: HealthKitDeliverySetting = .manual() // TODO Question @Paul : why does it default to manual?
) {
Expand All @@ -74,7 +74,7 @@ public struct CollectSample: HealthKitConfigurationComponent {

@available(*, deprecated, renamed: "init(_:predicate:delivery:)")
public init(
_ sampleType: HealthKitSampleType<some Any>,
_ sampleType: SampleType<some Any>,
predicate: NSPredicate? = nil,
deliverySetting: HealthKitDeliverySetting = .manual()
) {
Expand Down
61 changes: 31 additions & 30 deletions Sources/SpeziHealthKit/Configuration/HealthKitConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Spezi
/// to perform custom configuration actions.
public protocol HealthKitConfigurationComponent {
/// The HealthKit data types this component needs read and/or write access to.
var dataAccessRequirements: HealthKitDataAccessRequirements { get }
var dataAccessRequirements: HealthKit.DataAccessRequirements { get }

/// Called when the component is addedd to the ``HealthKit-swift.class`` module.
/// Components can use this function to register their respective custom functionalities with the module.
Expand All @@ -26,34 +26,35 @@ public protocol HealthKitConfigurationComponent {
}



/// Defines the object and sample types the ``HealthKit-swift.class`` module requires read and/or write access to.
public struct HealthKitDataAccessRequirements {
/// The object types a component needs read access to.
/// The ``HealthKit-swift.class`` module will include these object types in the
/// request when the app calls ``HealthKit-swift.class/askForAuthorization()``
public let read: Set<HKObjectType>
/// The object types a component needs write access to.
/// The ``HealthKit-swift.class`` module will include these object types in the
/// request when the app calls ``HealthKit-swift.class/askForAuthorization()``
public let write: Set<HKSampleType>

/// Creates a new instance, with the specified read and write sample types.
public init(read: some Sequence<HKObjectType> = [], write: some Sequence<HKSampleType> = []) {
self.read = Set(read)
self.write = Set(write)
}

/// Creates a new instance, containing the union of the read and write requirements of `self` and `other`.
public func merging(with other: Self) -> Self {
Self(
read: read.union(other.read),
write: write.union(other.write)
)
}

/// Merges another set of data access requirements into the current one.
public mutating func merge(with other: Self) {
self = self.merging(with: other)
extension HealthKit {
/// Defines the object and sample types the ``HealthKit-swift.class`` module requires read and/or write access to.
public struct DataAccessRequirements {
/// The object types a component needs read access to.
/// The ``HealthKit-swift.class`` module will include these object types in the
/// request when the app calls ``HealthKit-swift.class/askForAuthorization()``
public let read: Set<HKObjectType>
/// The object types a component needs write access to.
/// The ``HealthKit-swift.class`` module will include these object types in the
/// request when the app calls ``HealthKit-swift.class/askForAuthorization()``
public let write: Set<HKSampleType>

/// Creates a new instance, with the specified read and write sample types.
public init(read: some Sequence<HKObjectType> = [], write: some Sequence<HKSampleType> = []) {
self.read = Set(read)
self.write = Set(write)
}

/// Creates a new instance, containing the union of the read and write requirements of `self` and `other`.
public func merging(with other: Self) -> Self {
Self(
read: read.union(other.read),
write: write.union(other.write)
)
}

/// Merges another set of data access requirements into the current one.
public mutating func merge(with other: Self) {
self = self.merging(with: other)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import HealthKit

/// A ``HealthKit`` configuration component that requests read acess to HealthKit sample types.
public struct RequestReadAccess: HealthKitConfigurationComponent {
public let dataAccessRequirements: HealthKitDataAccessRequirements
public let dataAccessRequirements: HealthKit.DataAccessRequirements

/// Creates a HealthKit configuration component that requests read access to the specified `HKObjectType`s.
public init(_ objectTypes: some Sequence<HKObjectType>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import HealthKit
/// - Warning: Only request write access to HealthKit data if your app's `Info.plist` file
/// contains an entry for the `NSHealthUpdateUsageDescription` key.
public struct RequestWriteAccess: HealthKitConfigurationComponent {
public let dataAccessRequirements: HealthKitDataAccessRequirements
public let dataAccessRequirements: HealthKit.DataAccessRequirements

/// Creates a HealthKit configuration component that requests write access to the specified `HKObjectType`s.
public init(_ objectTypes: some Sequence<HKSampleType>) {
Expand Down
6 changes: 3 additions & 3 deletions Sources/SpeziHealthKit/HealthChart/HealthChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import SwiftUI
import HealthKit
import Charts
import SpeziHealthKit

Check warning on line 13 in Sources/SpeziHealthKit/HealthChart/HealthChart.swift

View workflow job for this annotation

GitHub Actions / Build and Test Swift Package / Test using xcodebuild or run fastlane

file 'HealthChart.swift' is part of module 'SpeziHealthKit'; ignoring import

Check warning on line 13 in Sources/SpeziHealthKit/HealthChart/HealthChart.swift

View workflow job for this annotation

GitHub Actions / Build and Test Swift Package / Test using xcodebuild or run fastlane

file 'HealthChart.swift' is part of module 'SpeziHealthKit'; ignoring import
import SpeziFoundation


Expand Down Expand Up @@ -49,7 +49,7 @@
public enum StatisticsAggregationOption: Sendable {
case sum, avg, min, max

public init(_ sampleType: HealthKitSampleType<HKQuantitySample>) {
public init(_ sampleType: SampleType<HKQuantitySample>) {
switch sampleType.hkSampleType.aggregationStyle {
case .cumulative:
self = .sum
Expand Down Expand Up @@ -404,7 +404,7 @@
let valuesRange = { () -> ClosedRange<Double>? in
var range: ClosedRange<Double>?
func imp(_ entry: HealthChartEntry<some Any>) {
guard let expectedRange = (entry.results.sampleType as? HealthKitSampleType<HKQuantitySample>)?.expectedValuesRange else {
guard let expectedRange = (entry.results.sampleType as? SampleType<HKQuantitySample>)?.expectedValuesRange else {
return
}
if let _range = range {
Expand Down Expand Up @@ -484,7 +484,7 @@
if let dataPoint = entry.makeDataPoint(for: element) {
let x: PlottableValue = .value("Date", dataPoint.date)
let y: PlottableValue = .value(name, dataPoint.value * (entry.results.sampleType == .bloodOxygen ? 100 : 1))
let s: PlottableValue = .value("Series", name)

Check warning on line 487 in Sources/SpeziHealthKit/HealthChart/HealthChart.swift

View workflow job for this annotation

GitHub Actions / Build and Test Swift Package / Test using xcodebuild or run fastlane

immutable value 's' was never used; consider replacing with '_' or removing it
SomeChartContent {
switch entry.drawingConfig.mode {
case .line:
Expand All @@ -498,7 +498,7 @@
.annotation { // TODO?
// let date = dataPoint.date.ISO8601Format(Date.ISO8601FormatStyle())
// Text("\(dataPoint.stringValue)\n\(date)")
Text("\(dataPoint.stringValue)")
Text(dataPoint.stringValue)
.font(.caption)
}
}
Expand Down
35 changes: 8 additions & 27 deletions Sources/SpeziHealthKit/HealthKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import SwiftUI
///
/// Then, you can configure the ``HealthKit`` module in the configuration section of your `SpeziAppDelegate`.
/// Provide ``HealthKitDataSourceDescription`` to define the data collection.
/// You can, e.g., use ``CollectSample`` to collect a wide variety of ``HealthKitSampleType``s:
/// You can, e.g., use ``CollectSample`` to collect a wide variety of ``SampleType``s:
/// ```swift
/// class ExampleAppDelegate: SpeziAppDelegate {
/// override var configuration: Configuration {
Expand Down Expand Up @@ -81,10 +81,10 @@ public final class HealthKit: Module, EnvironmentAccessible, DefaultInitializabl

// TODO why doesn't importing SpeziHealthKit as @testable work in the TestApp????(!)
/// (for testing purposes only) The data access requirements that resulted form the initial configuration passed to the ``HealthKit-swift.class`` module.
public let _initialConfigHealthKitDataAccessRequirements: HealthKitDataAccessRequirements
public let _initialConfigDataAccessRequirements: DataAccessRequirements

/// Which HealthKit data we need to be able to access, for read and/or write operations.
private var healthKitDataAccessRequirements: HealthKitDataAccessRequirements
private var dataAccessRequirements: DataAccessRequirements

/// Configurations which were supplied to the initializer, but have not yet been applied.
/// - Note: This property is intended only to store the configuration until `configure()` has been called. It is not used afterwards.
Expand All @@ -103,10 +103,10 @@ public final class HealthKit: Module, EnvironmentAccessible, DefaultInitializabl
) {
healthStore = HKHealthStore()
pendingConfiguration = config()
_initialConfigHealthKitDataAccessRequirements = pendingConfiguration.reduce(.init()) { dataReqs, component in
_initialConfigDataAccessRequirements = pendingConfiguration.reduce(.init()) { dataReqs, component in
dataReqs.merging(with: component.dataAccessRequirements)
}
healthKitDataAccessRequirements = _initialConfigHealthKitDataAccessRequirements
dataAccessRequirements = _initialConfigDataAccessRequirements
if !HKHealthStore.isHealthDataAvailable() {
// If HealthKit is not available, we still initialise the module and the health store as normal.
// Queries and sample collection, in this case, will simply not return any results.
Expand Down Expand Up @@ -147,7 +147,7 @@ public final class HealthKit: Module, EnvironmentAccessible, DefaultInitializabl
/// - Important: You need to call this function at some point during your app's lifecycle, ideally soon after the app was launched.
@MainActor
public func askForAuthorization() async throws {
try await askForAuthorization(for: healthKitDataAccessRequirements)
try await askForAuthorization(for: dataAccessRequirements)
}


Expand All @@ -164,8 +164,8 @@ public final class HealthKit: Module, EnvironmentAccessible, DefaultInitializabl
/// - Warning: Only request write access to HealthKit data if your app's `Info.plist` file
/// contains an entry for the `NSHealthUpdateUsageDescription` key.
@MainActor
public func askForAuthorization(for accessRequirements: HealthKitDataAccessRequirements) async throws {
self.healthKitDataAccessRequirements.merge(with: accessRequirements)
public func askForAuthorization(for accessRequirements: DataAccessRequirements) async throws {
self.dataAccessRequirements.merge(with: accessRequirements)
try await healthStore.requestAuthorization(
toShare: accessRequirements.write,
read: accessRequirements.read
Expand Down Expand Up @@ -262,22 +262,3 @@ public final class HealthKit: Module, EnvironmentAccessible, DefaultInitializabl
}
}
}





extension HKHealthStore {
// TODO remove?!
func spezi_requestPerObjectReadAuthorization(for type: HKObjectType, predicate: NSPredicate?) async throws -> Bool {
try await withCheckedThrowingContinuation { continuation in
self.requestPerObjectReadAuthorization(for: type, predicate: predicate) { success, error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: success)
}
}
}
}
}
Loading
Loading