Passive data updates are suited for apps that need to monitor Health Services data in the background and is intended for use cases that span hours, days or even longer. If you need to store or process health data when your app isn't running, and the user is not explicitly engaged in an exercise, you should use Health Service's passive client.
See the Passive Data and Passive Goals samples 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:
Groovy
dependencies { implementation "androidx.health:health-services-client:1.0.0-beta02" }
Kotlin
dependencies { implementation("androidx.health:health-services-client:1.0.0-beta02") }
Check capabilities
Before registering for data updates, check that the device can provide the type of data your app needs. Checking capabilities allows you to enable or disable certain features, or modify your app's UI to compensate for features that are not available.
val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
val capabilities = passiveMonitoringClient.capabilities.await()
// Supported types for passive data collection
supportsHeartRate =
DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
// Supported types for PassiveGoals
supportsStepsGoal =
DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}
Register for passive data
You can receive passive data through a service or a callback (or both). A service allows your app to receive data in the background when no part of your app is visible in the foreground. When you receive data in the background it is delivered in batches. The callback receives data at a slightly faster rate, but only while the app is running and the callback is successfully notified.
Whichever method you use, you first need to create a PassiveListenerConfig
that determines which data types to receive, as shown in the following example:
val passiveListenerConfig = PassiveListenerConfig.builder()
.setDataTypes(setOf(DataType.HEART_RATE_BPM))
.build()
To receive data using a callback, define and register the callback as shown in the following example:
val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
// TODO: do something with dataPoints
}
}
passiveMonitoringClient.setPassiveListenerCallback(
passiveListenerConfig,
passiveListenerCallback
)
// to remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()
Using a service is similar, but instead of creating a class derived from
PassiveListenerCallback
, derive from PassiveListenerService
, as shown in
the following example:
class PassiveDataService : PassiveListenerService() {
override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
// TODO: do something with dataPoints
}
}
passiveMonitoringClient.setPassiveListenerServiceAsync(
PassiveDataService::class.java,
passiveListenerConfig
)
Next, declare the service in your AndroidManifest.xml
. Require a Health
Services permission, which ensures that only Health Services is able to bind
to the service:
<service android:name=".PassiveDataService"
android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
android:exported="true" />
Interpret time
The data you receive from Health Services is batched, so you may receive data points of different types, or multiple data points of the same type, in the same batch. Use the timestamps included within these objects rather than the time they were received by your app to determine the correct ordering of events.
Obtain timestamps for each DataPoint
by first calculating the boot timestamp,
as shown in the following example:
val bootInstant =
Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())
This value can then be passed to
getStartInstant()
or
getEndInstant()
.
Restore registrations after boot
Passive data registrations don't persist across reboots. To receive data after a
device is restarted, re-create your registrations using a BroadcastReceiver
that
listens for the
ACTION_BOOT_COMPLETED
system broadcast.
The receiver shouldn't attempt to restore the registrations directly. Instead,
it should delegate this functionality to a WorkManager
worker. When the
device is starting up, Health Services may take ten seconds or more to
acknowledge a passive data registration request, and this may exceed the
allowable execution time of a BroadcastReceiver
. In contrast, WorkManager
workers have a
ten minute execution limit.
Your BroadcastReceiver
should look something like the following:
class StartupReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
// TODO: check permissions first.
WorkManager.getInstance(context).enqueue(
OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
)
}
}
class RegisterForPassiveDataWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
override fun doWork(): Result {
runBlocking {
HealthServices.getClient(appContext)
.passiveMonitoringClient
.setPassiveListenerCallback(...)
}
return Result.success()
}
}
To arrange for the system to execute this code when the device boots up, make
two changes to the AndroidManifest.xml
.
First, add the following permission as a child of <manifest>
:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Second, add the following receiver intent filter as a child of <application>
:
<receiver
android:name=".StartupReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Activity state
The passive client can also provide high-level information on user state, such as whether the user is sleeping. To receive these updates, follow these steps:
- Request the
ACTIVITY_RECOGNITION
permission. - Call
setShouldUserActivityInfoBeRequested(true)
in thePassiveListenerConfig
builder.
Override the onUserActivityInfoReceived()
method in your callback or service,
and make use of the returned UserActivityInfo
:
override fun onUserActivityInfoReceived(info: UserActivityInfo) {
val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
val userActivityState: UserActivityState = info.userActivityState
if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
// ...
}
}
Passive goals
You can configure a passive client to notify the app when passive goals are reached, such as the user completing 10,000 steps in a day.
To do this, create a goal, as shown in the following example:
val dailyStepsGoal by lazy {
val condition = DataTypeCondition(
dataType = DataType.STEPS_DAILY,
threshold = 10_000, // trigger every 10000 steps
comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
)
PassiveGoal(condition)
}
Add this goal to your PassiveListenerConfig
, as shown in the following
example:
val passiveListenerConfig = PassiveListenerConfig.builder()
.setDailyGoals(setOf(dailyStepsGoal))
.build()
Override the onGoalCompleted()
method in your callback or service
and make use of the returned PassiveGoal
:
override fun onGoalCompleted(goal: PassiveGoal) {
when (goal.dataTypeCondition.dataType) {
DataType.STEPS_DAILY -> {
// ...
}
}
}