StanfordSpezi / SpeziHealthKit

HealthKit module of the Stanford Spezi framework
https://swiftpackageindex.com/StanfordSpezi/SpeziHealthKit/documentation/
MIT License
17 stars 3 forks source link

Improve HealthKit Query Reusability #8

Open PSchmiedmayer opened 1 year ago

PSchmiedmayer commented 1 year ago

Use Case

The HealthKit component currently provides a good automated setup to collect HealthKit data that is defined in the Configuration of a Spezi application. Nevertheless, some Spezi applications need to create more custom queries and an improved setup that is more dynamic than the setup that is possible in the configuration.

Different Spezi Modules would also like to reuse the selective functionality of the HealthKit module in other mechanisms.

Problem

The HealthKit module provides a comprehensive implementation of several HealthKit queries that do not need to be reimplemented in Spezi applications with custom HealthKit queries. The current abstraction behind data sources provides a good encapsulation but limited extensibility.

Solution

We should add core parts of the functionalist as extensions to the existing HealthKit store types as well as the HealthKit queries. These extensions can be combined into a one-stop solution to query for HealthKit data, manage that query, and execute the query gathering a single result or an async sequence.

This abstraction should subsequentially be used in the HealthKit Module to power the automated Configuration-based API.

Alternatives considered

Input for alternatives is greatly appreciated. Please use the comments under this issue to discuss possible approaches and ideas. We are happy to support outside contributors who want to contribute to the Spezi ecosystem.

Additional context

No response

Code of Conduct

MihirJoe commented 8 months ago

@PSchmiedmayer Does this issue still need to be addressed?

PSchmiedmayer commented 8 months ago

@MihirJoe Yes, still, a great issue to look into.

The goal would be to make it easier to run one-off queries for HealthKit. Some use cases would allow you to easily display a chart showing the step counts from the last week, month, and year, as well as some UI to navigate between the weeks, months, and years.

Ideally, we provide a nice and convenient Swift type that can be used in a SwiftUI view that does the necessary query for HealthKit for us and provides the values in a convenient way. The query is re-evaluated if some of the configuration changes (e.g., the user jumps from a month view to a year view or switches between years), and the data is provided in a way that is easy to plot and interpret. HKSamples might still be the best way to make it accessible, but I could see some extensions on HKSample (or more specifically, a collection of HKSample sub-types) that would make it easier to plot this. The complete construct must use SwiftUI Observable and the new re-drawing mechanisms to make this efficient within a SwiftUI context.

A good way to get started would be to design a first example of how the API might be used by creating a common use case (as described above) and see how we can nicely extract this into smaller and then more reusable components. SwiftUI dynamic properties might also be a nice way to do this; I would suggest looking at SwiftData for some API inspirations.

MihirJoe commented 7 months ago

@PSchmiedmayer Thank you for providing these details. I have been diving deeper into this issue and have made some progress. I created a project from the template application to start playing around with how I want to query HealthKit data. Please see this repository: https://github.com/MihirJoe/SpeziTemplateApplication/tree/healthkit-reusability

So far, I have been able to effectively query Step Count data from the current day and also created an interactive Charts view where the user is able to select different date ranges (e.g. 7 days, 1 month, 3 months) and easily see their step count data. The README contains a simple diagram of the architecture and a short demo video of querying the step count data. I have plans of making this existing code a bit more reusable with other HKSample types and HKStatisticCollection types.

Of course, I will make a PR in the SpeziHealthKit module for you to review some of these changes, but it would be great to get your feedback on the initial functionality before I start solidifying some of the abstracted API implementation. Looking forward to your suggestions!

PSchmiedmayer commented 4 months ago

Thank you @MihirJoe for the example project and it’s great to see you in Stanford a few weeks ago!

Thank you for creating the example project; the display of charts and eventually highlighting this data in an application will be a great addition to SpeziHealthKit! @nriedman has been working on a great base infrastructure of a sample-type independent SwiftCharts-based display of HKSamples in our ENGAGE-HF app that will eventually land in SpeziHealthKit. I can see this and your work as a great combination of determining the public interface of what we want to do here.

One element that we will need to address first and that was the core element of this issue is building a better query reusability across the module. I am thinking about using a similar interface to SwiftData using an HKSampleQueryDescriptor (or small wrapper around this that is more Swifty) that can be used to easily query HealthKit data in any SwiftUI view that also automatically updates every time a new value is added. We should take advantage of the current elements in SpeziHealthKit that already provide this updating functionality but no internal storage that could be exposed to a SwiftUI view. The new updates to our module infrastructure in Spezi will make this reusability also way easier.

I think a first step would be to enable this double-use in a SwiftUI view that builds on a small wrapper on top of different HealthKit query descriptors so we don't re-invent the wheel. We should generally aim to do that for all our current Sample collection types in HealthKit and move away from our more custom initializers.

In the end, I could see a nice mechanism that looks close to this:

struct ExampleView: View {
    // Uses our small Swift-like query description wrappers, maybe we can even make the types more specialized depending on the query passed in, e.g., HKQuantitySamples
    @HealthKitQuery(descriptor: ...) var samples: [HKSample]

    var body: some View {
        HealthKitSampleChart(samples)
            .displayGranulary([.day, .week, .month, .halfYear, .year])
    }
}

In some way, we can then also create built-in HealthKitCharts that just get a sample type and we do the date selection and display types for the application completely automatically.