---
title: "PassiveMonitoringClient and MeasureClient on Wear OS"
canonical: "https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient"
cluster: "Watch Apps"
primary_query: "passivemonitoringclient measureclient wear os"
last_reviewed: "2026-10-03"
description: "Wear OS background and spot data: PassiveListenerConfig, PassiveListenerService, passive goals, reboot re-registration, and MeasureClient callbacks."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"PassiveMonitoringClient and MeasureClient on Wear OS\", AIFitnessAPI, https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient"
---

# PassiveMonitoringClient and MeasureClient on Wear OS

> Health Services gives Wear OS apps two clients besides ExerciseClient. PassiveMonitoringClient is for data over hours or days: you build a PassiveListenerConfig, then register a PassiveListenerService that Health Services binds to with batched data, a PassiveListenerCallback that gets unbatched data only while your app is alive, or both. Google states that passive registrations do not persist across reboots, so you re-create them from an ACTION_BOOT_COMPLETED receiver that hands off to WorkManager. MeasureClient is for a short spot reading while your UI is on screen: registerMeasureCallback takes a single DeltaDataType such as HEART_RATE_BPM, raises the sensor sampling rate until you call unregisterMeasureCallbackAsync, and Google says it is not for background capture or workout tracking.

- Canonical: https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient
- Last reviewed: 2026-10-03
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "PassiveMonitoringClient and MeasureClient on Wear OS", AIFitnessAPI, https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient

---

Which Health Services client fits which product is introduced on [Wear OS Health Services](/devices/wear-os-health-services). This page is the code for the two non-exercise clients, from Google's guides "Passive data updates" and "Take spot health measurements with MeasureClient" and the Jetpack reference, read on 2026-10-03. The workout path is [ExerciseClient in Kotlin](/watch-apps/wear-os-exerciseclient-kotlin).

## Choosing between the three clients

Google's own descriptions draw the lines:

| Client | Google's description | Use it for |
|---|---|---|
| `PassiveMonitoringClient` | "suited for long-lived experiences where data updates are relatively infrequent" | All-day steps, daily goals, background heart rate trends, sleep state |
| `MeasureClient` | "suited for short-lived experiences, such as while the user looks at your app UI" | A heart rate readout on one screen |
| `ExerciseClient` | manage a workout, set goals, rapid updates "as long as the exercise belongs to your app" | A workout the user started |

Google's MeasureClient guide is blunt about the boundary: "`MeasureClient` is not suitable for workout tracking." The reference says the same about background use.

## PassiveMonitoringClient: configure once, choose a channel

```kotlin
val passiveClient = HealthServices.getClient(context).passiveMonitoringClient

val capabilities = passiveClient.getCapabilitiesAsync().await()
val canHeartRate = DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
val canStepGoal = DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals

val config = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM, DataType.STEPS_DAILY))
    .build()
```

The reference says Health Services "will typically reject requests made for `DataType`s which are not supported," so the capability check is not optional.

There are two delivery channels, and the reference says they operate independently, with "each channel" receiving "its own stream of data."

**A service, for when your app is not running.**

```kotlin
class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // write to Room; do not do network work here
    }
}

passiveClient.setPassiveListenerServiceAsync(PassiveDataService::class.java, config).await()
```

Declare it in the manifest with `android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"` and `android:exported="true"`. The reference explains the permission: it is there "in order to ensure that Health Services is the source of the binding." Service data "will be batched", though "Any requested goal, user activity, or health event updates will not be batched."

**A callback, for while your app is alive.**

```kotlin
passiveClient.setPassiveListenerCallback(config, object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) { }
    override fun onRegistrationFailed(throwable: Throwable) { }
})
```

Callback updates "are sent on generation (they will not be batched)", but if the callback cannot be notified, "the request will automatically be unregistered."

Both registrations are "unique per subscribing app. Subsequent registrations will replace the previous registration." That one line causes real bugs: if a tile registers heart rate and a settings screen later registers only steps, heart rate silently stops. Build one config from the union of what every feature needs, in one place.

To stop, call `clearPassiveListenerServiceAsync()` or `clearPassiveListenerCallbackAsync()`. The reference warns that "Data will not be delivered after this call," so call `flushAsync()` first if you need pending batches. Flushes are "subject to throttling by Health Services."

## Order by timestamp, not by arrival

Because passive data is batched, Google says you may receive several types, or several points of one type, in one batch, and should "Use the timestamps included within these objects rather than the time they were received by your app." Point times are relative to boot. Convert with `Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())` and pass the result to `getStartInstant()` or `getEndInstant()`. Day boundaries then become your problem; see [time zones and day boundaries](/architecture/timezones-and-day-boundaries).

## Registrations do not survive a reboot

Google states it directly: "Passive data registrations don't persist across reboots." The documented fix is a `BroadcastReceiver` for `ACTION_BOOT_COMPLETED` that does not register directly but enqueues a `WorkManager` job, because "Health Services might take 10 seconds or more to acknowledge a passive data registration request, and this might exceed the allowable execution time of a `BroadcastReceiver`." The manifest needs `android.permission.RECEIVE_BOOT_COMPLETED` and the receiver's intent filter. Check permissions inside the worker before re-registering; a user may have revoked them while the watch was off.

## Daily types, passive goals and activity state

The `_DAILY` data types are the passive client's natural unit. Google's reference defines each with the same boundary: for example `STEPS_DAILY` is "The total step count over a day, where the previous day ends and a new day begins at 12:00 AM local time." The same midnight-local-time boundary appears in the definitions of `CALORIES_DAILY`, `DISTANCE_DAILY`, `FLOORS_DAILY` and `ELEVATION_GAIN_DAILY`.

A passive goal wraps a `DataTypeCondition`:

```kotlin
val dailySteps = PassiveGoal(
    DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000,
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
)
val goalConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailySteps))
    .build()
// override onGoalCompleted(goal: PassiveGoal) in the service or callback
```

For high-level user state, request `ACTIVITY_RECOGNITION`, call `setShouldUserActivityInfoBeRequested(true)` on the builder, and override `onUserActivityInfoReceived`. Google's sample reads `UserActivityState.USER_ACTIVITY_ASLEEP` and comments that `stateChangeTime` "may be in the past!"

## Background heart rate needs a background permission

Google's permissions page sets the rule by target API level. If your app targets API level 36 or higher and "uses `PassiveMonitoringClient` to access body sensor information in the background, request the `READ_HEALTH_DATA_IN_BACKGROUND` permission." For targets between 33 and 35 inclusive, "request both the `BODY_SENSORS` and `BODY_SENSORS_BACKGROUND` permissions instead." Steps, calories and distance types sit under `ACTIVITY_RECOGNITION`.

## MeasureClient: register, read, unregister

```kotlin
val measureClient = HealthServices.getClient(context).measureClient

val supported = DataType.HEART_RATE_BPM in
    measureClient.getCapabilitiesAsync().await().supportedDataTypesMeasure

val callback = object : MeasureCallback {
    override fun onAvailabilityChanged(dataType: DeltaDataType<*, *>, availability: Availability) {
        // e.g. not on the wrist
    }
    override fun onDataReceived(data: DataPointContainer) {
        // render the latest HEART_RATE_BPM point
    }
}

// when the screen appears
measureClient.registerMeasureCallback(DataType.HEART_RATE_BPM, callback)
// when it disappears
measureClient.unregisterMeasureCallbackAsync(DataType.HEART_RATE_BPM, callback)
```

Facts from the reference that shape this code:

- It takes a `DeltaDataType`, one per callback, so aggregate types like `HEART_RATE_BPM_STATS` are not what it is for.
- Registering "is expected to increase the sample rate on the associated sensor(s)," and the callback runs "until the app is killed or `unregisterMeasureCallbackAsync` is called." Tie registration to the screen's lifecycle. Google's guide says it is "important to minimize the amount of time that your callback is registered."
- The callback runs on the main thread unless you pass an `Executor`.
- Even live capture "can still be sent out in batches depending on the application processor state."
- "Existing subscriptions made with the `PassiveMonitoringClient` are also expected to get the data generated by this client." A spot reading also lands in your own passive stream, which matters if you count readings.

## Where the data goes next

Passive data is the natural feed for a tile; [Wear OS tiles](/watch-apps/wear-os-tiles) covers rendering from a local cache. Battery trade-offs between the three clients are on [watch app battery](/watch-apps/watch-app-battery). If you also write the readings to the phone-side store, the record types are in [Health Connect records](/health-connect).

## FAQ

### Do Health Services passive data registrations survive a watch reboot?

No. Google's Passive data updates guide states that passive data registrations do not persist across reboots. Its documented fix is a BroadcastReceiver for ACTION_BOOT_COMPLETED that enqueues a WorkManager worker to re-register, rather than registering inside the receiver, because Health Services might take 10 seconds or more to acknowledge a registration at startup, which can exceed a BroadcastReceiver's allowed execution time. Declare RECEIVE_BOOT_COMPLETED in the manifest, and check permissions inside the worker before registering again.

[Permalink](https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient#faq-1)

### Why did my PassiveMonitoringClient stop delivering heart rate after I registered for steps?

Because a new registration replaces the old one. Google's reference for setPassiveListenerServiceAsync and setPassiveListenerCallback says each registration is unique per subscribing app and that subsequent registrations replace the previous one, and that data stops for a type when you register again without it. If two features register separately, the last one wins. Build a single PassiveListenerConfig from the union of all the data types and goals your app needs, and register it from one place.

[Permalink](https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient#faq-2)

### Does a MeasureClient heart rate reading also reach my passive listener?

Google's MeasureClient reference says existing subscriptions made with PassiveMonitoringClient are also expected to get the data generated by this client. So a spot reading taken on screen can appear again in your PassiveListenerService or callback. If you count readings or store both streams, deduplicate by data point timestamp, which Google says you should use for ordering anyway, rather than assuming the two channels never overlap.

[Permalink](https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient#faq-3)

### What permission does background heart rate need on Wear OS 6?

Google's Health Services permissions page says that if your app targets API level 36 or higher and uses PassiveMonitoringClient to access body sensor information in the background, it should request READ_HEALTH_DATA_IN_BACKGROUND, alongside READ_HEART_RATE for heart rate itself. Apps targeting API levels 33 through 35 inclusive request BODY_SENSORS and BODY_SENSORS_BACKGROUND instead. The migration steps cap the legacy permissions with android:maxSdkVersion set to 35, so one manifest can serve both ranges.

[Permalink](https://aifitnessapi.com/watch-apps/wear-os-passive-monitoring-measureclient#faq-4)
