HealthKit provides you the tools to smartly manage health data anywhere, whether across multiple HealthKit-enabled devices such as iPhone and Apple Watch or with an external server to share data across a care team. In this session, we'll dive into managing data versions via HealthKit's built-in sync identifier metadata, how to detect changes in health data using an HKAnchoredObjectQuery, and cover best practices for ensuring you're always working with the right data everywhere.
Hello and welcome to WWDC. Hello everyone and welcome to Synchronize Health Data with HealthKit. My name is Netra and I'm a software developer on the Healthcare team. Our users store their most private and personal information on our devices in the form of health data. On Apple platforms, HealthKit is the foundation that provides easy access to the data. HealthKit also enables all of the rich experiences you have created in your apps. As a result, we are all part of this large health ecosystem. The goal of this health ecosystem is to empower our users. Our users may access their health data from any part of this ecosystem. From Apple Watch, from the apps released by Apple or from all your amazing apps. As such, we never want to surprise our users with the changes we make to their health data. While we all want to be good citizens of this health ecosystem, it can get quite challenging.
In this talk, we here at HealthKit are going to help you do just that. So let's remember that our goal is to empower all users. Users must always remain in control of their data and changes to health data must always reflect user intent. Today, we will look at two important topics. First, we will look at monitoring changes in HealthKit. We know that data can be read or written from any part of the health ecosystem. This health data can be used to power multiple types of apps or data visualizations. Our apps must be equipped to react appropriately to the changes in HealthKit. We will then dive into maintaining an external data store in sync with HealthKit, and we'll see how HealthKit can make it really easy for you. We'll begin with our first topic, Monitoring Changes in HealthKit. Let's build an app to work through these concepts. This is an app for patients recovering from an injury.
This app lets patients track their daily step count. As time goes on, they want to monitor the progress in their daily step count. Additionally, our patient also visits the physical therapist a couple of times a week and completes certain walking tests. The test is a 'six minute walking distance test'.
It measures the distance covered by a patient while walking on a flat surface for six minutes. The physical therapist enters the results of this test on the machine and data is synced over to the users iPhone as a weekly report. One of the best ways to monitor such important data is via a graph. Our patient can easily see the uptrend or downtrend in their steps count without being too concerned with actual values. You'll have a graph that is of great interest to our patient and the physical therapist.
It tracks the patient's total step count over each day of the past week.
Now, steps can be recorded from both Apple Watch and iPhone. We want to ensure that we get an accurate count of steps taken in a day. The first thing you do when creating a graph like this is to reach for the HKStatisticsCollectionQuery. This is the first query you should reach for when created most graphs. You might have already learnt all about this query in our previous talk, Getting Started with HealthKit. If not and if it is new to you, you might want to go watch that talk first. In fact, we'll use this SmoothWalker app created in that talk to build our app. The graphs that we just created needs to be sent to the remote server, so that the physical therapist who is interested in the user's progress, can read them as well.
How would this look like? First, we'd send our initial graph of the week to the server. Next, if there are any changes to the data for the week, for instance, with more steps collected. The new data for the week would have to be sent over to the server. For our purposes here, we are considering the external data to be a remote server, but it might as well be an external database maintained locally on the device. It could be using Core Data or even splite database. Now, we know that we can use HKStatisticsCollectionQuery to load data. We'd need to run the query at a regular cadence to fetch new data. This could be at app launch and maybe every few hours after that.
But there are some downsides to using it for reacting to new changes. If data does not change often enough, we would be running the HKStatisticsCollectionQuery multiple times leading to redundant calculation. We would also be sending all the data every single time. This would be a waste of network resources. Healthkit has another tool specifically for use cases like this. It's called the HKAnchoredObjectQuery. The HKAnchoredObjectQuery allows us to monitor updates to the health database. It provides a snapshot of changes in the healthy database. This snapshot includes both new samples and deleted samples. Let's see how this query works. As the name suggests, an anchored object query requires an anchor. An anchor represents a specific point in time in the evolution of the health database. Health data could have been added or deleted after this point in time. This anchor allows you to identify all the samples you last received from the query.
When you provide HealthKit with this anchor, HealthKit will only return the changes since that point. Now initially, for the first query our anchor will be nil. HealthKit at this time has samples A, B and C. When you execute the query, HealthKit returns all the data based on the data type you specified.
In this case, you will receive samples, A, B and C in your update handler. The anchor is updated, and HealthKit gives you the new anchor in the updateHandler.
It is the last point in time that HealthKit returned samples in the updateHandler.
Let's say there were more samples added since your updateHandler was last called.
These include samples D and E. Additionally, sample B has since been deleted.
For every subsequent query run, only the changes since the previous anchor would be returned in the updateHandle. These will include samples D and E, the deletion of sample B, but nothing about A and C. When we use the HKAnchoredObjectQuery, or any query in HealthKit, we must think a little about the type of data and use case we are dealing with. In the case of steps, we don't really care about the actual samples itself. In fact, Apple Watch was released five years ago now. That's a lot of data being generated by these devices. That's a lot of data to sync up. We just want our cumulative statistic, not every single sample. There are other cases where we might care about the individual samples.
These could be samples that are not generated very often. Each data type can be treated differently. We must spend some time thinking about how we want to query and sync our data. The task we are trying to accomplish drives the type of query we want. Querying for the minimal amount of data has its performance benefits as well. For our current use case, we just want the statistical graph to be available to the physical therapist. How do we do this? We can always run over our samples from the HKAnchoredObjectQuery and compute the graph, but why not make the two queries work together.
With the AnchoredObjectQuery we can set our predicate and sample type to exactly what we're looking for. When the AnchoredObjectQuery updates you with changes and HealthKit, you can look at the dates of the returned samples and use that to create and run the statistics collection query. The statistics returned for those days can then be sent to the remote computer as updated graph data. Now we just send the new data to the server. This again, might be a local CoreDara model or an NSURLSession for a remote server. To understand us better, we'll take a look at how this would look like in code. First, we'll set the parameters for the anchored object query.
The sample type is step count. We'll set the anchor parameter using the persistent anchor. Persisting the anchor allows us to only retrieve the changes in HealthKit since a last query. We need the same behavior for the initialResultsHandler and the updateHandler, and so we'll set the same block for the handler variable. In the handler, we'll unwrap the samples returned from HealthKit.
We'll create a predicate from the samples. This might be the date for which the samples were retrieved. This is the predicate we'll be using to initialize our statistics collection query. Here, we also need to update the persisted anchor.
At the end of this block, we can call the fetchStatistics method that creates the HKStatisticsCollectionQuery using the predicate. We'll initialize the HKAnchoredObjectQuery with the parameters we set. These include sampleType, a nil predicate, the anchor and lastly the resultsHandler and the updatesHandler. Finally, we execute the query on the health store. Let's see how the fetchStatistics method creates the HKStatisticsCollectionQuery.
Once again, we set our parameters. We want our statistics collection query bucketed by days. To achieve this, we'll set the anchor date to Monday at midnight and an interval parameter of 1 day. The sample type will once again be step count. We can then initialize the HKStatisticsCollectionQuery with these parameters. The options would be cumulative sum since we want the total step count for each day. As you can see, we have provided the predicate created in the anchored object query over here. In our initialResultsHandler, we unwrap the statisticsCollection and then simply send over this data to our remote server. And as always, we'll only get our updates after we execute the query on the health store. We just saw how you can send data from HealthKit to an external server, now we need to get to the next step which is receiving data from the external store and save it to HealthKit as well.
Like you saw earlier, a patient visits the physical therapists office a couple of times a week and completes six minute walk test. The six minute walk test is the amount of distance covered by an individual while walking for six minutes on a flat surface. It is often used by physical therapists to gauge the patient's exercise capacity. This year we introduced a new set of Mobility data types in HealthKit. One of the data types is the SixMinWalkTestDistance This works perfectly for the purposes of our app. We can save test data received from the physical therapist as SixMinWalkTestDistance samples. In our app, the physical therapist records the six minute walk test values and a weekly report is synced to the patient's device.
The weekly report contains a graph of the SixMinuteWalkTestDistance for each day of the week. We also have the individual samples from the graph displayed underneath it. This is in case the physical therapist or patient wants to dig deeper into each individual test result. This takes us back to the idea that each data type needs to be treated differently.
We must think about the type of data we are dealing with and what our use case is.
The SixMinuteWalkTestDistance is not written to the HealthKit very frequently, and the patient or the physical therapist might be interested in each individual test sample. So unlike steps which we saw earlier, in this graph it is worth plotting each sample. Earlier, we were syncing step samples from the device to a remote server. Now, we have this weekly report on the physical therapist server. We want to sync it from the server to the patient's device, and save the corresponding samples in HealthKit. This will allow the patient to view their SixMinuteWalkTestDistance graph on their app, along with the individual test samples. When saving changes to HealthKit, there are a couple of things to keep in mind. We only want to save incremental samples in HealthKit. Simply removing and then re-saving all the data can result in an inconsistent state of the user's health data. New samples reflect new health data about the user. For instance, a new SixMinuteWalk test or new steps covered. Maybe even the change in weight recorded by the user.
When deleting a sample, you need to ensure that it is actually a sample that was previously written by your app. You can't delete data you didn't explicitly save yourself. A good best practice here is to first query for the sample and then delete it. Adding or deleting a sample should always reflect user intent. If the user didn't mean to delete a sample, then you should probably not be deleting it. Now there are some challenges that arise here.
What if the physical therapist wanted to update a specific test. For example, for a test on the 18th of June, the patient covered 400 meters in a six minute walking test. This data was later updated by the physical therapist to reflect an error. The distance covered was actually 450 meters. Now this can be a little tricky. When you edit a sample, you need to actually delete and add in your new sample. If you don't, you could be saving duplicated samples.
This means you have to query for the sample, match it to the exact sample the physical therapist has edited, and then save a new sample. If there are no edits needed to be made to the other samples, you need to make sure that during your changes you are not saving a duplicated sample for any of those either.
Health data is available on all of the patient's devices, their iPhones and Apple Watch. The change in samples need to be reflected correctly across all devices. If you have saved the sample on one device, you need to make sure that you are not saving the sample again on another device.
All this time, you have to ensure that you are correctly reflecting user intent.
This may appear to be really complex, but HealthKit actually makes this really easy for you. HealthKit contains two metadata keys known as HKMetadataSyncIdentifier and HKMetadataSyncVersion, the sync identifier is a string and the version is a number. The identifier allows us to recognize a sample anywhere in the health eco-system, across any of the user's devices.
The version helps us understand when the sample has been updated. When you set a sync identifier on a sample, HealthKit ensures that duplicate copies of the sample are not saved in the user's health database. A combination of the sync identifier and the version allows HealthKit to update samples only when the version number has increased. Additionally, all operations done using sync identifiers are transaction safe. That means if there was any error, you can be rest assured that your data is in a consistent state. Health data is available on all of the users devices.
Sync identifiers allow you to maintain samples in a consistent state across devices. In your remote server you have a sample with an identifier and a version one. Your app syncs a sample from the remote server to the patient's iPhone. Considering this in the first sample HealthKit saves it successfully. Now, when HealthKit realizes a new sample has been saved, it syncs this over to all of the patient's devices. If the patient had an Apple watch, it would sync it over to the Watch. If your remote server were also syncing data to your app on Watch, and you tried to save the sample again, HealthKit would see that sample already exists, and ignore it.
Now, if the physical therapist decides to update the distance completed in the six minute walk test, we would update the sample by keeping the sync identifier consistent, but increasing the version number. But this sample is synced to the patient's device, HealthKit notices that the version number has increased.
It overwrites the previous sample with a new sample. The sample would again be synced to all of the patient's devices. If the remote server syncs the version two of the sample to Apple Watch, HealthKit would see that the sample already exists and ignore the sample. As you can see, HealthKit will manage all the conflict resolution while saving and syncing. The challenging task of versioning and syncing has been reduced to simply maintaining consistent identifiers.
We saw the patients weekly report earlier but how do we go about actually modeling this data? One way to do this, is by representing the weekly report as a Report class. This report class can be identified with a high level sync identifier.
The report class will contain a list of all the SixMinWalkTestDistance HKSamples from that week. Each sample can then contain a sync identifier metadata key which is derived from the high level report identifier.
This way each sample can be uniquely referenced across different weeks. Data can be synced from the remote server to the patient's device in the form of the report class model and individual HKSamples from this list can be saved to HealthKit. Let's take a look at this in the demo. We have already created this app SmoothWalker, in our previous talk Getting Started with HealthKit. This project is also available for download as sample code on the Developer website. Now we want to create this weekly report View Controller in our app. When you press the fetch button, we want to pull the 6 minute walk samples from the server and populate our view controller with it.
Let's take a look at the WeeklyReportTableViewController class. When you select the Fetch button, the didTapFetchButton method will be called.
Here we want to pull the serverResponse from the network, we then want to call the handleServerResponse method. We'll now implement the handleServerResponse method with this server response. Let's implement the handleServerResponse method. We want to pull the weekly report from the serverResponse and save the corresponding samples to HealthKit. The first thing we'll do is to pull the weekly report from the server response.
After this we’ll loop over the samples in the weekly report. As you can see here, we are looping over the weekly report samples and returning on HKQuantity samples for each of them. In this loop, forced with other parameters for each HKQuantity sample. We have a quantity, which has a unit meter, and the value is pulled from the server health sample. The sample type is six minute walk test distance and finally we have the start and end date. We'll then initialize the HKQuantity sample with these parameters. We have the sample type, the quantity, the start and end date, and for now the metadata is nil.
The samples returned from this loop need to be saved to HealthKit. So now we save the samples to HealthKit. As you can see, we have saved all these samples using the health store. In the completionHandler we'll load the new data from HealthKit to the view controller. Let's run this code and see what it looks like. When we select the fetch button, the view controller is populated with the samples from the weekly report. However, when we select the fetch button again we can see that duplicated samples are saved to HealthKit. This is incorrect. We don't want to duplicated data on HealthKit.
Let's see how this changes when we include metadata in the HKQuantity sample. We create the metadata dictionary and added to our list of parameters.
We'll pull the sync identifier from the server health sample and add it to the key HKMetadataKeySyncIdentifier. Similarly we'll also pull the sync version from the server health sample and add it to the key HKMetadataKeySyncVersion we'll then add this metadata dictionary to a list of parameters in the HKQuantitySample. Let's run this call again and see what it looks like. For the purposes of this demo I've added code to delete all the six minute walk samples in HealthKit on app launch. However, let's remember that when deleting samples always be careful and ensure that we're reflecting user intent. Now on selected fetch, the six minute walk samples are displayed in the weekly report. On selecting fetch multiple times however there are no duplicate examples. As you can see saving the sample with the HKMetadataKeySyncIdentifier and the HKMetadataKeySyncVersion ensures that there are no duplicated samples in HealthKit. We have now modeled the data backing this graph. Synchronizing your external data with HealthKit is not as challenging as it might appear to be. HealthKit provides the tools that allow you to efficiently monitor changes in the health ecosystem, as well as maintain your data consistently across multiple devices. Let me leave you with some best practices when working on synchronizing your health data. When making changes to users data ensure that you're always reflecting user intent.
Users should not be taken aback or surprised by the changes to their health data.
Think about ways in which you can run efficient queries, maybe you can consider combining queries to fetch and sync minimal amount of data. Finally, since health data exists across multiple devices, use sync identifiers and version numbers to keep data consistent across devices. We have only touched a small part of the health ecosystem on Apple platforms. But there is so much more you can do here. When working with health data, think about the security and privacy implications of what you are trying to do. This is especially important when maintaining health data on an external data store. Apple has a lot of resources that can help you with this. If you want to create the beautiful graph visualizations we showed you today, take a look at the CareKit framework.
CareKit is an open source framework for developing apps, specifically around managing your health and providing care. Finally, HealthKit has multiple other features that you can use to create rich health experiences. These range from Workouts, Clinical Health Records and High Frequency data types. We are so excited to see the amazing apps that you create using all these HealthKit resources.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.