Active data and exercises

The Wear OS form factor is well-suited for situations when other form factors are less desirable, such as during exercise. In these situations, your app may need frequent data updates from sensors, or you may be actively helping the user manage a workout. Health Services provides APIs that make it easier to develop these types of experiences.

See the Exercise sample on GitHub.

Add dependencies

To add a dependency on Health Services, you must add the Google Maven repository to your project. For more information, see Google's Maven repository.

Then, in your module-level build.gradle file, add the following dependency:

dependencies {
  implementation "androidx.health:health-services-client:1.0.0-alpha1"
  // For Kotlin, this library helps bridge between Futures and coroutines.
  implementation "com.google.guava:guava:30.1.1-android"
  implementation "androidx.concurrent:concurrent-futures-ktx:1.1.0"
}

In your AndroidManifest.xml file, add the following inside of the manifest tag so your app can interact with Health Services. For more information, see Package visibility.

<queries>
    <package android:name="com.google.android.wearable.healthservices" />
</queries>

Using MeasureClient

With the MeasureClient APIs, your app registers callbacks to receive data for as long as you need. This is meant for situations in which your app is in use and requires rapid data updates. Ideally you should create this with a foreground UI so that the user is aware. Note that MeasureClient is not suitable for workout tracking. See Use ExerciseClient instead.

Check capabilities

Before registering for data updates, check that the device can provide the type of data your app needs. Checking capabilities beforehand allows you to enable or disable certain features, or modify your app's UI to compensate for capabilities that are not available.

Kotlin


val healthClient = HealthServices.getClient(this /*context*/)
val measureClient = healthClient.measureClient
lifecycleScope.launch {
    val capabilities = measureClient.capabilities.await()
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesMeasure
}

Java


HealthServicesClient healthClient = HealthServices.getClient(this /*context*/);
ListenableFuture<MeasureCapabilities> capabilitiesFuture =
        healthClient.getCapabilities();
Futures.addCallback(capabilitiesFuture,
        new FutureCallback<Capabilities>() {
            @Override
            public void onSuccess(@Nullable Capabilities result) {
                boolean supportsHeartRate = result
                        .supportedDataTypesMeasure()
                        .contains(DataType.HEART_RATE_BPM)
            }

            @Override
            public void onFailure(Throwable t) {
                // display an error
            }
        },
        ContextCompat.getMainExecutor(this /*context*/));

Register for data

Each callback you register is for a single data type. Note that some data types might have varying states of availability. For example, data types that rely on location may not be available until a GPS fix is acquired.

It's important to minimize the amount of time that your callback is registered, as callbacks cause an increase in sensor sampling rates, which in turn increases power consumption.

val heartRateCallback = object : MeasureCallback {
    override fun onAvailabilityChanged(type: DataType, availability: Availability) {
        // Handle availability change.
    }

    override fun onData(dataPoints: List<DataPoint>) {
        // Inspect data points.
    }
}
val healthClient = HealthServices.getClient(this /*context*/)

// Register the callback.
lifecycleScope.launch {
    healthClient.measureClient
        .registerCallback(DataType.HEART_RATE_BPM, heartRateCallback)
        .await()
}

// Unregister the callback.
lifecycleScope.launch {
    healthClient.measureClient.unregisterCallback(heartRateCallback).await()
}

Using ExerciseClient

Health Services provides first-class support for workout apps through the ExerciseClient. With ExerciseClient, your app can control when an exercise is in progress, add exercise goals, and get updates about the exercise state or other desired metrics.

Check capabilities

For each ExerciseType, certain data types are supported for metrics and for exercise goals. Check these capabilities at startup. Use ExerciseCapabilities.getExerciseTypeCapabilities() with your desired exercise type to see what kind of metrics you can request, what exercise goals you can configure, and what other features are available for that type.

val healthClient = HealthServices.getClient(this /*context*/)
val exerciseClient = healthClient.exerciseClient
lifecycleScope.launch {
    val capabilities = exerciseClient.capabilities.await()
    if (ExerciseType.RUNNING in capabilities.supportedExerciseTypes) {
        runningCapabilities =
            capabilities.getExerciseTypeCapabilities(ExerciseType.RUNNING)
    }
}

Inside the returned ExerciseTypeCapabilities, supportedExerciseTypes lists the DataTypes that you can request data for. This varies by device, so take care not to request a DataType that isn't supported or your request might fail.

The supportedGoals and supportedMilestones fields are maps where the keys are DataTypes and the values are a set of ComparisonTypes which you can use with the associated DataType. Use these to determine whether the exercise can support an exercise goal that you want to create.

// Whether we can request heart rate metrics.
supportsHeartRate = DataType.HEART_RATE_BPM in runningCapabilities.supportedDataTypes

// Whether we can make a one-time goal for aggregate steps.
val stepGoals = runningCapabilities.supportedGoals[DataType.AGGREGATE_STEP_COUNT]
supportsStepGoals = 
    (stepGoals != null && ComparisonType.GREATER_THAN_OR_EQUAL in stepGoals)

Manage the exercise lifetime

Health Services supports at most one exercise at a time across all apps on the device. If an app starts an exercise, it will cause any current exercise in another app to be terminated. Your app should check for other running exercises before starting your exercise, and react accordingly. For example, ask the user for confirmation before starting.

lifecycleScope.launch {
    val exerciseInfo = exerciseClient.currentExerciseInfo.await()
    if (exerciseInfo.exerciseTrackedStatus == ExerciseTrackedStatus.OTHER_APP_IN_PROGRESS) {
        // Ask the user for confirmation.
    }
}

When you want to start an exercise, create an ExerciseConfig to configure the exercise type, the data types for which you want to receive metrics, and any exercise goals. Exercise goals consist of a DataType and a condition. Exercise goals are triggered either once a condition is met (a one-time goal), or each time an amount is added (a milestone goal). The following sample shows one goal of each type:

const val CALORIES_THRESHOLD = 250f
const val DISTANCE_THRESHOLD = 1_000f // meters

suspend fun startExercise() {
    // Types for which we want to receive metrics.
    val dataTypes = setOf(
        DataType.HEART_RATE_BPM,
        DataType.AGGREGATE_CALORIES_EXPENDED,
        DataType.AGGREGATE_DISTANCE
    )
    // Create a one-time goal.
    val calorieGoal = ExerciseGoal.createOneTimeGoal(
        condition = DataTypeCondition(
            dataType = DataType.AGGREGATE_CALORIES_EXPENDED,
            threshold = Value.ofFloat(CALORIES_THRESHOLD),
            comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
        )
    )
    // Create a milestone goal. To make a milestone for every kilometer, set the initial
    // threshold to 1km and the period to 1km.
    val distanceGoal = ExerciseGoal.createMilestone(
        condition = DataTypeCondition(
            dataType = DataType.AGGREGATE_DISTANCE,
            threshold = Value.ofDouble(DISTANCE_THRESHOLD),
            comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
        ),
        period = Value.ofDouble(DISTANCE_THRESHOLD)
    )
    val config = ExerciseConfig.builder()
        .setExerciseType(ExerciseType.RUNNING)
        .setDataTypes(dataTypes)
        .setExerciseGoals(listOf(calorieGoal, distanceGoal))
        .setAutoPauseAndResume(false)
        .build()
    HealthServices.getClient(this /*context*/)
        .exerciseClient
        .startExercise(config)
        .await()
}

ExerciseClient provides additional methods to pause, resume, and end an exercise that your app started. For certain exercise types, you can also mark laps. Health Services provides an ExerciseLapSummary with metrics aggregated over that period.

Register for exercise state updates

Exercise updates are delivered to a listener. Your app can only register a single listener at a time. Your listener can be registered even if your app doesn't currently have an exercise in progress, but you will only receive updates about exercises your app owns.

val listener = object : ExerciseUpdateListener {
    override fun onStateUpdate(update: ExerciseUpdate) {
        // Process the latest information about the exercise.
        exerciseStatus = update.status // e.g. ACTIVE, USER_PAUSED, etc.
        activeDuration = state.activeDuration // Duration
        latestMetrics = state.latestMetrics // Map<DataType, List<DataPoint>>
        latestGoals = state.latestAchievedGoals // Set<AchievedExerciseGoal>
        latestMilestones = state.latestMilestoneMarkerSummaries // Set<MilestoneMarkerSummary>

    }
    override fun onLapSummary(lapSummary: ExerciseLapSummary) {
        // For ExerciseTypes that support laps, this is called when a lap is marked.
    }
}
val exerciseClient = HealthServices.getClient(this /*context*/).exerciseClient

// Register the listener.
lifecycleScope.launch {
    exerciseClient.setUpdateListener(listener).await()
}

// Unregister the listener.
lifecycleScope.launch {
    exerciseClient.clearUpdateListener(listener).await()
}

Maintain your app's presence

While you can manage an exercise from an activity, it's likely that your activity could be stopped for a variety of reasons. The user might switch to another app or return to the watch face. The system might display something over the top of your activity, or the screen might turn off after a period of inactivity. Here are some ways to make sure your app stays prominent while you have an exercise in progress so that the user can return to your app:

  1. Use a Service. If your activity is stopped, your Service can continue running by moving to the foreground and showing a persistent notification. You can also use the Ongoing Activity API to show your notification on more surfaces within Wear OS.
  2. Support Ambient Mode. Instead of the screen turning off, your app can constantly display information on-screen. See Ambient Mode for more details.