New Ways to Work with Workouts

Session 707 WWDC 2018

Tracking workouts with Apple Watch is popular with fitness professionals and novices alike. See how the new workout API streamlines the entire lifecycle of a workout, complete with ability to restart a session after a crash to prevent you from losing data. Learn how the new workout classes, types, and queries provide quick access to baseline sample data you would typical calculate without limiting access to the fine grained samples you may occasionally require.

[ Music ]

[ Applause ]

Good morning, everyone, and welcome to New Ways to Work with Workouts.

My name is Niharika [phonetic].

I'm an engineer on the Fitness team here at Apple, and today I'm going to be joined by my colleague Karim, an engineer on the Health team.

And we are so excited to share with all of you some of the brand-new features and capabilities that have been added to HealthKit in iOS 12 and watchOS 5.

Apple Watch was released 3 years ago now, and since the beginning, our users have loved engaging with its health and fitness features.

They've loved closing their rings and earning achievements, sharing with their friends, but they've especially loved to do workouts.

And the watch has really become just the beginning of what is a vibrant, evolving ecosystem, and that is thanks to contributions from developers like all of you.

In just the last quarter of 2017, there were over 200 million downloads of apps in the Health and Fitness category on the App Store, and that number itself is incredible.

Two hundred million is so, so cool.

But more than that, it's a testament to 2 things.

Number 1, it's a testament to the dedication all of you have shown in getting these great user experiences out there.

But number 2, it's a sign that people really care.

People are engaged in the space, and they are so excited about what all of you are putting out there.

And at the heart of this ecosystem are these 2 apps, Activity and Health.

Activity is a home for you to visualize just that your activity data.

You can see your workouts.

You can see calories, exercise minutes.

And Health is all of that and more.

And of course, at the heart of all of that, which is the reason we're here today, is HealthKit.

And so we have a ton of exciting things to talk about today, but before we get started with any of that, we have to talk about something extremely important, and that is privacy and authorization, because data is sensitive, especially health data, and it is so important to make sure that you have a comprehensive privacy story when working with your apps.

Next, Karim is going to share our brand-new workout features, which have made it easier than ever before to develop a robust workout app.

And lastly, I'm so excited to share our new Quantity series API, which is a brand-new way to both store and relate high-frequency data.

So let's get started.

Like I mentioned, privacy and authorization has to come at the beginning of any development story.

And at Apple, we think about privacy in 1 simple way: Privacy is about people.

And HealthKit is designed with this in mind.

HealthKit is designed to put users in control of their data.

Users have the ability to grant access as well as revoke whenever they want.

And as developers, all of you are the last link of that puzzle, and we want to make sure we are giving you the tools to make sure that your users' privacy is respected.

And we think about this in a few simple rules.

Number 1 is this idea of proportional collection, and this is the idea that you should only be collecting data relative to what your app needs.

And this isn't a fixed set of data.

At the beginning, this amount might be small.

As your use cases, as your features grow, the amount of data you need to collect might be more, but it is so important that you only collect what you need at a specific time.

And second is the idea that HealthKit authorization can change.

And this one's a little nuanced.

For example, if a user resets their location and privacy data, their location and privacy setting, it is so important for you to make sure you're honoring that, and it is your responsibility to make sure you do that.

And the best way to do this is to treat HealthKit as the source of truth because HealthKit will update to reflect that authorization status, and it is so important to make sure you don't cache any of that and make sure you're always turning to HealthKit to ask what data you have access to.

And these 2 ideas can be distilled into 3 simple rules.

And when we're thinking about privacy and authorization, you should ask for only what you need, only when you need it, and every single time you need it.

And in code, this is just as simple.

Let's say I'm creating a workout app, and I'm just getting started, and, of course, I need to start with privacy and authorization.

First, since I'm creating a workout app, I want to share the workout type, so I explicitly declare that I'm going to be sharing the workout type.

Next, since I'm just getting started, I only have a few things that I'm trying to track.

I want to be able to track my user's heart rate, calories, as well as distance walking and running.

And this is all I need right now, and so that's all I'm going to ask for.

And lastly, on my healthStore, I request authorization for just that.

I request my types to share and my types to read.

And with these 3 simple steps, we can really make sure that we are taking the steps necessary to respect our user's privacy.

And privacy is so important because great apps have great privacy, and it's so important that you give attention to this at the beginning of any development process.

And now that we have access to our users' data, I'm so excited to introduce Karim [phonetic] to tell you all about the brand-new workout features.

[ Applause ]

Hello, everyone.

Thank you so much for joining us today.

I am really excited to tell you all about the new workout API.

If you're new to HealthKit, you'll see just how easy it is to create a full-featured workout app from scratch.

If, on the other hand, you have a workout app on the App Store, we'll show you all of the great new features of the API that your app can benefit from.

So let's dive in.

First, I would like to take a look at the general workout app life cycle.

So let's say we're building a workout app to track our users' activity, and the first step is to set up the app.

So at this state here, we know that the user wants to perform a workout, the type of activity, and we need to make sure that our UI is ready for that.

Once the setup is done, we can go ahead and start the workout.

So after this point, the user will be actively working out.

And then, of course, after some time, the user will want to end the workout.

And from there, we can save this workout and all of its associated data in HealthKit.

So this is the general life cycle of a workout app.

Now, let's see what happens when the user is active.

With your user interface at the center, one of the tasks that you want to accomplish is to collect data that is relevant to the workout and displayed in your user interface.

And you also want to make sure that the user can control the state of the session so that the user can pause or resume the workout.

And if you're familiar with HealthKit, you might know about HKWorkoutSession on watchOS.

HKWorkoutSession allows you to handle a few of these steps in the life cycle.

It will prepare the device's sensors for data collection set so that you can accurately collect data that's relevant to the workout like calories and heart rate.

It will also allow your application to run in the background when the workout is active.

You'll be, also be able to use HKWorkoutSession to control the state of this, the workout.

And finally, you can also generate events like swimming laps in segments.

But of course, this is not enough, and you also need to collect data that is relevant to the workout that's generated by the device and be able to save it all in HealthKit.

And to do just that, I'm really happy to introduce a new class, HKWorkoutBuilder.

The workout builder is your one-stop shop to collecting data and saving it in HealthKit.

So the builder will create and save an HKWorkout object, which is a representation of a workout in HealthKit.

You can then add samples, events, and also custom metadata to the builder while building the workout.

And if you're on watchOS, you can use its subclass, HKLiveWorkoutBuilder.

So it's watchOS only, and because it's a subclass of the workout builder, it has all of the great benefits of the builder, but because it's on watchOS and it works closely with HKWorkoutSession, it has all of these really cool features like automatic sample and event collection, which we'll talk about later.

So let's go back to the workout app life cycle, and we'll see how we can now set up and start the workout using the new workout builder.

So first, we need to create a workout builder.

And to do that, we use the initializer, and we have to pass a healthStore object, a configuration which contains information like the workout type, whether it's indoor or outdoor, and other information.

And finally, an optional device if the data comes from an external device, for example.

Once my builder is created, all I need to do is call beginCollection and pass a start date.

It's that simple.

If you are on watchOS, you can use the live workout builder.

And so first, you need to create a workout session by passing the healthStore and workoutConfiguration, and then you do not create the builder yourself, but you retrieve it directly from the session by using this call.

Once we have the session and builder, we can go ahead and start them up.

And so it's as easy as calling startActivity on the session and beginCollection on the builder by passing a start date.

So this is how you set up and start a workout using the builder.

Now, let's see how we can collect data displayed directly in our user interface and be able to control the state of the workout when the user requests it.

So to collect data, if we want to add samples that are relevant to the workout, like calories, or distance, or heart rate, we can do that using a very simple call, builder.add, and give it an array of HKSamples.

If we have events that we want to add to the workout, it's very similar.

We just call builder .addWorkoutEvents.

For metadata, you guessed it.

With the dictionary of metadata, we just call builder.addMetadata and pass the dictionary.

But on Apple Watch, because the device is on your wrist and has all this wide range of sensors, you can actually generate data like calories and distance.

And wouldn't it be cool if we could have all of this data automatically collected by the builder?

Starting with watchOS 5, we now have automatic data collection, and it all starts with HKLiveWorkoutDataSource.

So the data source will collect samples that are relevant to the workout currently running.

You do, however, have the option which types you want to collect, so you can add or remove types as you wish.

And this is how it works.

First, we create a data source.

And here, I pass the healthStore as well as the workoutConfiguration.

And since I'm passing the configuration that contains information about the activity type, the data source will be able to infer which types should be collected for this workout.

Next, I simply need to set the data source on the live workout builder.

And optionally, you can add or remove types that are being collected.

So let's say I want to add the type.

I can specify which type I want to add here as well as an optional predicate if I want to further limit which types are being collected.

And finally, we just call dataSource.

collectStatistics for and pass the type as well as the optionalPredicate.

So now that we have data being collected, let's see how we can display it directly in our user interface.

So every time new data comes in the builder's delegate method, workout builder did collect data of collected types will be called.

And so here, if, for example, I'm interested in the heart rate type, I will make sure that it's one of the collected types.

And then, I simply make use of the builder's statistics for quantity type method, which gives me an HKStatistics object that will contain information, in this case, like minimum, maximum, average, as well as most recent value for the heart rate that's recorded throughout this workout.

From there, I can simply update my user interface.

One thing that's also very common in workout apps is to have an elapsed timer.

And so, of course, we start the timer at the beginning of the workout, but that's not enough because pause or resume events will affect duration of the workout.

So every time new events are added to the builder, the builder's delegate method workoutBuilderDid CollectEvents will be called.

And from here, you can simply use the builder's elapsedTime property, which will give you the current elapsed time for the workout.

It's that easy.

Next, let's see how we can control the state of the workout.

So first, I'd like to talk about all of the different states that the workout session can be in.

And the first one is Not Started.

So here, the workout has not started yet, and we're not yet, the user is not performing the workout.

So after that, we'll be moving to the Prepared state.

And so the Prepared state will put the system in Session Mode, which means that your application will have background running, the sensors will also be ready to track the activity, but the workout has not started yet.

So if you, for example, have a countdown in your app before a workout, you can move the session to the Prepared state before that to make sure that the device is ready to start tracking the activity.

After that, we can move to the Running state.

And here, the user is actively working out.

And of course, the user can move to the Paused, back to Running, and back and forth between these 2 until the workout is Stopped.

So here, the device is still in Session Mode, which means that your app is still running in the background, so that gives you some extra time that, if you needed to save the workout.

After that, we can simply end the workout.

And from there, the system will exit Session Mode.

And to be able to move between all these states, you simply need to use these calls directly on the workout session.

So that's how we can collect data displayed in our user interface as well as control the state of the session.

Let's see now how we can end the workout and save it in HealthKit.

So to end the workout, you first call session.end, and then you also call builder.

endCollection and pass the workout's end date.

At this point, no data is going to be collected for this workout.

And then, if you want to save the workout in HealthKit, you just call builder.

finishWorkout, and you will get back a workout object in the completion handler that's already saved in HealthKit with all of its associated data.

So that's how you now build a workout app using the new API.

And now, I'd like to show you how this all works in a demo where we will be building a workout app for Apple Watch.

[ Applause ]

So I'm actually currently building a workout app that will allow my users to track running workouts.

And it's a very simple app.

I have a big Run button here.

When I tap on it, I'm presented with this user interface that will show an elapsed time of their workout as well as some metrics, like calorie burned, the most recent heart rate, as well as the distance that's run in the workout.

Of course, my user will have the option to control the state of the workout, so my user can pause, resume, and end the workout.

So I already went ahead and implemented the user interface.

So all that's left for me now is to use the new workout API and make my app functional.

And the first thing that we need to do here is make sure that the project is properly set up.

And so to do that, I'm going to head over to my Project Settings, go to the Capabilities tab, and here just make sure that the HealthKit Capability is turned on.

And you should also do the same for your WatchKit app extension.

So again, make sure that the HealthKit Capability is turned on.

Once I did that, we should also add the 2 purpose strings in the info.plist file that will tell my users why I need access to their health data.

And so here, the first string is "health share user description" that tells my users why I would like to save data in HealthKit as well as help update usage description.

That will tell them why I need to read data in HealthKit.

Once that's done, I can now start using the new workout API.

And of course, the first step before being able to use the API is to make sure that I request authorization for the data that I need.

And so, of course, every app is different.

And in my case, if we go back to my application, I will be saving a workout here.

So that's one of the types that I will be needing access to.

And I also need to be able to read calories, heart rate, as well as distance.

So let's do that.

And of course, we need to make sure that we always ask for authorization when we need it.

In my case here, I want to do that every time the user is presented with this UI so that I can make sure that I have the authorization before starting a workout.

So this view here is backed by my workoutStartView Watchkit interface controller, and I'm going to go ahead and implement the authorization in the didAppear method.

So first, my typesToShare here is workout because I want to be able to save a workout at the end.

Next, I would like to read heartRate, activeEnergyBurned, as well as distanceWalkingRunning.

Once I have my typesToShare and typesToRead, I just need to call requestAuthorization on the healthStore and pass the types.

So let's go ahead and run this code now and see what happens.

And because the watch screen is small, the user will be presented with an authorization sheet on the phone.

So you need to make sure to also handle authorization on your iPhone app.

Going to go ahead and dismiss this view here.

And so let's open my iPhone app.

And here, I'm presented with an authorization sheet where I can decide if I want to grant this application access or not.

For the purpose of this demo, I'm going to turn all the categories on and tap Allow.

And that's how we have now granted the authorization to my application.

Next, we can now finally get started with the workout API.

So when I tap on this button here, this view will be passed, all the workoutConfiguration object that contains information about the workout.

In this instance here, it's a running workout.

And from there, I can go ahead and set up my workout.

So this view here is backed by my workout session, WatchKit interface controller, and I'm going to go do the setup in the awake method.

So first, I'm going to create the HKWorkoutSession as well as the HKLiveWorkoutBuilder.

So we create the session using its initializer, and we pass the workout configuration to it.

From there, we simply retrieve the builder directly from the session.

Creating a session might fail if the workout configuration is invalid, so that's why I wrap my code here in a do-catch block and dismiss if I have any failures.

Next, let's set up the session and builder.

So in this case, I want my interface controller to be the delegate of both the session and the builder.

And because I also want to collect samples generated by the device, I'm going to use here a live data source and pass it a workout configuration so that the types are automatically inferred for me.

Finally, I simply start the session as well as the builder.

So now that my workout is started, the first thing that I need to do is to start my elapsedTime timer.

And so I'm going to do that here in the callback of the beingCollectionCall.

I have a method here that will do that for me.

And of course, because pause and resume events can also affect the duration, I'm going to have this call in the builder's didCollectEvent call as well.

Now, let's implement this method.

So in my UI, I'm using a WatchKit interface timer.

If you're not familiar with this object, this object needs a date in the past from which it will start counting from.

So I'm going to go ahead and create my date object using the builder's elapsedTime property.

And because I want the date to be in the past, I'm going to make sure that I have a minus sign here.

Next, I simply dispatch on the main queue because I'm doing UI work, and then I just set the date on the timer.

I also need to make sure that the timer is only running when the session is running as well.

So let's do that.

First, I grab the session state, and then, again, I dispatch on the main queue.

And if the session is running, I'm going to start the timer.

And if it's not, I'm going to call Stop on the timer.

And that's all you need to do to get the timer tracking the elapsed time.

Next, let's make sure that our metrics displayed in the UI are also accurate.

So every time new samples are collected by the builder, this method here will be called.

And so here, I'm just going to go and iterate over the collectedTypes.

And, in my case, all of the data that I'm interested in is quantity samples.

So I'm going to make sure that I only handle those.

And from there, we can make use of the new builder's method, builder.statistics, for quantity types, which will give me an HKStatistics object that contains information like minimum, maximum, average, and most recent values for each type.

So I grab the statistics object here, and from there, I have a method that I already implemented that will give me the user interface label for this particular quantity type.

And from there, it just update my label using the statistics object.

Next, let's make sure that we can control the state when the user requests it.

So every time my user will tap the Pause button, this method here will be called.

And from there, I simply need to call session.pause.

For resume, it's very similar.

I'm going to do session.resume.

And finally, to end the workout, we're going to call session.end and then builder.endCollection and pass the workout's end date.

And from there, we're just going to call finishWorkout to actually save the workout and all of its associated data in HealthKit.

And then, we just dismissed it here.

So let's go ahead and run this code and see what happens now.

So I'm going to go ahead and start my workout here.

And as we see now, I have my timer updating.

I have data directly collected by the builder and displayed right in my user interface.

And of course, I can also react to the state changes, so going to go ahead and pause the workout.

My timer has paused.

Data collection as well.

I can resume.

And just like that, with a few lines of code, I now have a fully functional workout app.

[ Applause ]

So I'm going to stop the workout now, and let's see what we have just saved in the health app on the iPhone.

So this is my workout here that I have just saved, and we have now a very rich representation of this workout saved directly in HealthKit.

I have the device details as well as all of the samples that were associated, like heart rate, distance, and active energy, and, also, the workout events that were generated when I paused and resumed the session.

And that's how easy it is now to build a full-featured workout app on Apple Watch.

[ Applause ]

But of course, sometimes things don't go as planned.

Let's say I'm running my first marathon and I'm really excited about it and using my favorite workout app to track this marathon, but I only realize at the end that the app actually crashed sometime in the middle, making me lose all of this data.

Well, with watchOS 5, we are introducing workout recovery that will solve that problem for you.

[ Applause ]

If your application happens to crash during an active workout, we will automatically relaunch it and give it a chance to recover the workout.

The workout session as well as the builder will be recovered in their previous state, so you should not call startActivity or beginCollection on the builder.

If you are using a data source to automatically collect data, you do need to set it up again, and this is how it works.

If your app is relaunched after a crash, we will be calling the WatchKit extension delegate method, handleActive WorkoutRecovery.

From there, you simply need to create a healthStore object and then call recoverActive Workout Session, and you will get back a session in the completion handler.

It's that simple.

So this is our new workout API.

It makes it easier than ever to build great workout apps.

We also have a new Quantity series API.

And for that, I'd like to hand it back to my colleague Niharika to tell you more about it.

[ Applause ]

Isn't that incredible?

In less than 10 minutes, Karim was able to build a fully functional workout app.

And this is so important because, like I mentioned earlier, our users love doing workouts.

And with these workouts come a lot of data.

Let's say I'm interested in making a soccer app.

I really want to understand exactly how my users are moving, and I want to track the distance they're moving as they go side to side, across the field during the duration of a workout.

And what happens when we start the workout is that samples start coming in.

My user first goes 2.25.

They then go 1.6.

They go a little more.

And then, they finish it off.

And in the past, each one of these distances would be stored as 1 individual HKQuantity sample.

Each one would be independent, and they would be stored separately.

And so we thought that, wouldn't it be great if you could actually just have 1 sample that tracks that cumulative total but is still backed by those individual quantities behind it?

And that's why we've introduced HKCumulative QuantitySeriesSample, which is a brand new sample type that allows you to more efficiently store high-frequency data, and it's great for 2 reasons.

Number 1, you only have to store 1 sample that is backed by multiple quantities, and so you're more efficiently storing the high-frequency data that comes from things like workouts.

And number 2, you now have a sense of connection between the quantities that make up a sample.

So our new quantity series sample is a subclass of HKQuantitySample, which some of you may already be familiar with.

And if you've interacted with HKQuantitySample before, our new sample type will be very much the same.

So why would you want to use this?

Let's say you're interested in data visualization.

Your app requires complex charting or graphs, and you're trying to really visualize the data that your users have in a beautiful way.

We actually suggest that you continue using our HKStatisticsQuery or our HKStatistics CollectionQuery.

And that's because those are preexisting queries that do just this.

You have access to really fine-grained, rich data, and these queries will already get the fine-grained data on our new series samples, so you won't have to do any extra work to get those details.

If, for example, you're interested in data analysis, you want to go 1 step deeper.

You want to understand the actual quantities that make up a sample.

We suggest you use our new series sample query.

And lastly, if you're like me and have a soccer app [laughs] or any app that has really high-frequency data collection, we suggest that you use our new Quantity series sample builder, which is a brand-new way for you to actually be able to create those new cumulative Quantity series samples and store that data in a more efficient way.

So let's start with our sample query.

Like I mentioned, this is a way for you to be able to really look deeper and understand the individual HK quantities that make up a Quantity series sample.

And in code, we, of course, first want to start with figuring out where we're going to store our new quantities.

In my case, I'm going to store it in an array of quantities.

We next want to initialize our query.

We initialize it with the sample that we're interested in.

And in our completion handler is where the bulk of the work happens.

In my case, I have a method called analyzeQuantity which is going to do the work that I'm interested in with the quantity.

In my case, that's adding it to the array of quantities that I declared earlier.

And lastly, we execute our query.

It's really that simple.

For those of you familiar with some of our other queries, this behaves in a very similar way.

Next, we have our Quantity series sample builder, which, like I mentioned, is really great for apps that do have high-frequency data ingestion because it's a more efficient way to store that data but also allows you to kind of create connections between that.

So first, we, of course, want to start with creating our sample builder.

We initialize with the healthStore and we initialize it with the quantityType that we're interested in.

Next, for whatever cadence you're interested in inserting samples, you do that.

Let's use my soccer app as an example.

I am super interested in the actual distances that my users are moving, so I'm going to declare the meter unit as the quantity that I'm interested in, and I insert that into my seriesBuilder.

And at the end, we call finish on my series, and we're actually going to get the new Quantity series sample and be able to have stored that in a more efficient way.

And so we're really, really excited for you to start implemented our new Quantity series APIs in your apps because it's a brand-new way to store your data more efficiently, and its ability to be able to relate data together really allows for some interesting analysis.

So we have talked about a lot of interesting things, and we think that the best way to think about them is actually as parts of a whole because together they really make up a way for you to respect your users' user experience from start to finish.

Of course, we have to start with respecting user privacy.

Our users are giving access to one of their most sensitive data, and it is so important for us to make sure that we're treating it with the respect that it deserves.

Next, with the brand-new workout features Karim mentioned, we're able to create seamless user experiences.

With things like workout crash recovery, we're able to really deliver an experience to our users that they expect.

And lastly, by implementing our new Quantity series APIs, you're respecting the device's capabilities and really making sure that, from start to finish, you are giving your users the best experience possible.

And as the health and fitness space and as workout apps just keep growing and users keep using them more, we're so excited to see how these steps come together to create really incredible experiences.

So for more information, please check out our developer documentation, but we'd actually love to see you in person today.

We're going to be at a lab today from 1 to 3 as well as our Health, Fitness, and Research Get-Together later this evening, and we'd love to answer any of your questions, and hear your stories, and meet you in person.

We also want to mention that we have a brand-new features in HealthKit, and we'd love for you all to check out their talk, Accessing Health Records with HealthKit.

So thank you so much.

We're so excited to see what you all do with this.

Thank you.

[ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US