23 Jan 2016

Apple Health isn't private. It's painful.

In October 2015 (3 months ago) I started working on integrating Apple Health with Exist for iOS. Lots of Exist users (or potential users) don’t have dedicated fitness tracker hardware, and are quite happy with the activity and sleep tracking apps available on their phones. We don’t connect to many of those apps, because very few of them have APIs, but many of them do push data into Apple Health. We can get that data out and send it to the user’s Exist account.

Unfortunately, Apple’s decisions about how Apple Health should work have made it a rocky process. In many cases I’ve had to work around situations where the user would assume my app was bad, or broken, due to the way Apple Health works. In some cases the best workaround I’ve come up with is simply to explain to users in my app what the restrictions are and why they might be confused about the situation.

In short, Apple Health’s design has made for bad user experience in apps that read that data. (I’m not sending any data into Apple Health, so I’m only speaking from the perspective of designing an app that reads Health data.)

Allowing access to data

Perhaps the most frustrating decision I’m dealing with is purported to be Apple’s way of protecting users’ data. When I want to read a user’s Apple Health data, I present them with a way to turn on this option in the UI of my app. This triggers an OS-level modal showing the user which types of data my app has requested access to, and letting the user either accept all, accept some, or deny all.

Now here’s where things go belly up: I have no way of knowing what the user did in that modal. There is no way for me to find out whether the user did or didn’t accept my request to read their data. What I can do is query the user’s Health data and see what I get back. If I get non-zero data, I can safely assume the user allowed me access to that data type. But if I get zero values, one of two things could have happened: either the user denied me access to that data type, or they allowed access and they don’t have any data for the day(s) I’m querying. And I have no way to know which it is.

Here’s what the Apple docs say about this:

To help prevent possible leaks of sensitive health information, your app cannot determine whether or not a user has granted permission to read data. If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store.

Initially I checked what data I could get for the past seven days, assuming that any data types that returned all zero values had not been authorised. That is, until a user asked me why they kept getting errors when trying to connect Apple Health. They had given me access to just one of the data categories I’d requested, (I request steps, distance, and floors) but for that category they actually had no data. I’m not sure why the user thought it made sense to connect Apple Health if they were only giving me access to the one data type their phone wasn’t able to track, but that’s what they did. And my app kept returning errors, because as far as I could tell, I couldn’t access any of their data, so I couldn’t successfully connect Apple Health to their Exist account.

I don’t want to waste the phone’s resources by getting zero values from Health and sending them to the Exist server, so returning an error in this case and ignoring the data is really the best approach. The problem is, it makes users think my app is broken, when it’s actually a mixture of frustrating Apple decisions, and my approach to work around them.

So for now I’m sticking with this approach, but I’m also adding some info to the app to help explain why a user might be getting errors they don’t expect. This is a really hacky approach, and it doesn’t feel good, but for now it seems like supporting an Apple Health integration is worth the hassle.

If all the people who’ve asked us for Apple Health support don’t actually use it, I may end up changing my mind.

Apple could fix this, of course. Simply return a BOOL when I ask if I have access to each data category.

I don’t understand how this bad user experience is in any way protecting the user’s data. How is it better protection for the user to not tell me when they’ve denied access to their data? I can’t imagine any user would prefer the current set-up to letting Apple return a simple BOOL to tell me the user is not giving me their data.

Sleep data inconsistencies

Initially I thought I’d support sleep data as well as activity data from Apple Health, but I ran into some issues when creating test data to work with.

Because Apple hasn’t designed any best practices, there’s very little consistency among services that write data into Apple Health. Sleep is one data type where this gets particularly tricky, as it’s commonplace for fitness tracker hardware to provide both time in bed data and time asleep data—and it should go without saying that these should be different amounts of time. We’ve integrated Jawbone UP, Fitbit, Misfit, and Withings into Exist, and they all provide these data types.

On mobile, however, it’s still a Wild West in terms of fitness tracking. Any indie developer can create a tracker app without any idea of best practices or the science behind what they’re doing. So we end up with huge disparities in the data users have.

For instance, Sleep++, the first sleep tracking app for the Apple Watch, doesn’t track any difference between time in bed and time asleep. It writes both values into Apple Health, but they’re exactly the same, meaning one of them is always incorrect. (I believe it tracks from the time you start it until you stop it the next morning, so time asleep would be the one that’s always incorrect, since you don’t fall asleep immediately when you hit the button.)

Another example is the data Misfit shares with Apple Health. It writes sleep data, but it only writes time asleep, not time in bed (despite the fact that Misfit devices track both).

The problem with these disparities is essentially garbage in, garbage out: if we let users send bad data to Exist, they’re going to get bad results out. Our correlations will be incorrect, for instance, if their time in bed and time asleep data matches exactly. We may be telling them that things correlate to sleeping longer which actually don’t, because they’re lying awake for hours after telling Sleep++ to start tracking.

Exist relies on good data to provide useful insights, trends, and correlations. As we integrate with more data sources, it’s up to us to determine what data quality we require, and the consistency we need across different services providing the same data types.

So far I haven’t got a solution for sleep data from Apple Health. I believe I can choose which sources to accept data from, but this would mean individually testing and white-listing each source we confirm provides quality sleep data to Apple Health. It would also lead to a bad user experience for anyone with sleep data from a source I haven’t approved. There are just too many variables and qualifiers for this set-up to work and make sense for most users.

Ideally, Apple would suggest best practices to Health developers to encourage consistency among sources writing the same types of data.

For now, we’re not supporting sleep data from Apple Health because the alternative is not good enough.