The following example shows you how to read raw data as part of the common workflow.
Read data
Health Connect allows apps to read data from the datastore when the app is in the foreground and background:
Foreground reads: You can normally read data from Health Connect when your app is in the foreground. In these cases, you may consider using a foreground service to run this operation in case the user or system places your app in the background during a read operation.
Background reads: By requesting an extra permission from the user, you can read data after the user or system places your app in the background. See the complete background read example.
The Steps data type in Health Connect captures the number of steps a user has taken between readings. Step counts represent a common measurement across health, fitness, and wellness platforms. Health Connect lets you read and write step count data.
To read records, create a ReadRecordsRequest and supply
it when you call readRecords.
The following example shows how to read step count data for a user within a
certain time. For an extended example with SensorManager,
see the step count data guide.
suspend fun readStepsByTimeRange(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
StepsRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (record in response.records) {
// Process each record
}
} catch (e: Exception) {
// Run error handling here
}
}
You can also read your data in an aggregated manner using
aggregate.
suspend fun readStepsByTimeRange(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.aggregate(
AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
// The result may be null if no data is available in the time range
val stepCount = response[StepsRecord.COUNT_TOTAL]
} catch (e: Exception) {
// Run error handling here
}
}
Read mobile steps
With Android 14 (API level 34) and SDK Extension version 20 or higher,
Health Connect provides on-device step counting. If any app has been granted
the READ_STEPS permission, Health Connect starts capturing steps from the
Android-powered device, and users see steps data automatically added to
Health Connect Steps entries.
To check if on-device step counting is available, you need to verify that the device is running Android 14 (API level 34) and has at least SDK extension version 20. You can use the following code:
val isStepTrackingAvailable =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 20
Mobile steps captured by Health Connect have their
DataOrigin set to the package name android. If your app
simply reads aggregated step counts using aggregate and
doesn't filter by DataOrigin, on-device steps are automatically included in
the total.
If your app needs to read on-device steps, or if it displays step data
broken down by source application or device, you can query for records
where the DataOrigin is android. If your app shows attribution for step
data, you should attribute data from the android package to the current device.
You can do this by using a label such as "Your phone", retrieving the device
name with Settings.Global.getString(resolver, Settings.Global.DEVICE_NAME),
or inspecting the Device field in the record's metadata.
The following example shows how to read aggregated mobile step count data by
filtering for the android data origin:
suspend fun readStepsByTimeRange(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.aggregate(
AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
dataOriginFilter = setOf(DataOrigin("android"))
)
)
// The result may be null if no data is available in the time range
val stepCount = response[StepsRecord.COUNT_TOTAL]
} catch (e: Exception) {
// Run error handling here
}
}
On-Device Step Counting
Diving deeper into the on-device step counting feature:
- Sensor Usage: Health Connect utilizes the
TYPE_STEP_COUNTERsensor fromSensorManager. This sensor is optimized for low power consumption, making it ideal for continuous background step tracking. - Data Granularity: To conserve battery life, step data is typically batched and written to the Health Connect database no more frequently than once per minute.
- Attribution: As mentioned earlier, all steps recorded by this on-device
feature are attributed to the
androidpackage name in theDataOrigin. - Activation: The on-device step counting mechanism is active only when at
least one application on the device has been granted the
READ_STEPSpermission within Health Connect.
Background read example
To read data in the background, declare the following permission in your manifest file:
<application>
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" />
...
</application>
The following example shows how to read step count data in the background for a
user within a certain time by using WorkManager:
class ScheduleWorker(private val appContext: Context, workerParams: WorkerParameters):
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
// Read data and process it.
...
// Return success indicating successful data retrieval
return Result.success()
}
}
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Check if necessary permission is granted
val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions()
if (PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND !in grantedPermissions) {
// Perform read in foreground
...
} else {
// Schedule the periodic work request in background
val periodicWorkRequest = PeriodicWorkRequestBuilder<ScheduleWorker>(1, TimeUnit.HOURS)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"read_health_connect",
ExistingPeriodicWorkPolicy.KEEP,
periodicWorkRequest
)
}
} else {
// Background reading is not available, perform read in foreground
...
}
The ReadRecordsRequest parameter has a default pageSize value of 1000.
If the number of records in a single readResponse exceeds the
pageSize of the request, you need to iterate
over all pages of the response to retrieve all records by using pageToken.
However, be careful to avoid rate-limiting concerns.
pageToken read example
It is recommended to use pageToken for reading records to retrieve all
available data from the requested time period.
The following example shows how to read all records until all page tokens have been exhausted:
val type = HeartRateRecord::class
val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofDays(7))
try {
var pageToken: String? = null
do {
val readResponse =
healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = type,
timeRangeFilter = TimeRangeFilter.between(
startTime,
endTime
),
pageToken = pageToken
)
)
val records = readResponse.records
// Do something with records
pageToken = readResponse.pageToken
} while (pageToken != null)
} catch (quotaError: IllegalStateException) {
// Backoff
}
For information about best practices when reading large datasets, refer to Plan to avoid rate limiting.
Read previously written data
If an app has written records to Health Connect before, it is possible for that app to read historical data. This is applicable for scenarios in which the app needs to resync with Health Connect after the user has reinstalled it.
Some read restrictions apply:
For Android 14 and higher
- No historical limit on an app reading its own data.
- 30-day limit on an app reading other data.
For Android 13 and lower
- 30-day limit on app reading any data.
The restrictions can be removed by requesting a Read permission.
To read historical data, you need to indicate the package name as a
DataOrigin object in the dataOriginFilter parameter of your
ReadRecordsRequest.
The following example shows how to indicate a package name when reading heart rate records:
try {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = HeartRateRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
dataOriginFilter = setOf(DataOrigin("com.my.package.name"))
)
)
for (record in response.records) {
// Process each record
}
} catch (e: Exception) {
// Run error handling here
}
Read data older than 30 days
By default, all applications can read data from Health Connect for up to 30 days prior to when any permission was first granted.
If you need to extend read permissions beyond any of the
default restrictions, request the
PERMISSION_READ_HEALTH_DATA_HISTORY.
Otherwise, without this permission, an attempt to read records older than
30 days results in an error.
Permissions history for a deleted app
If a user deletes your app, all permissions, including the history permission, are revoked. If the user reinstalls your app and grants permission again, the same default restrictions apply, and your app can read data from Health Connect for up to 30 days prior to that new date.
For example, suppose the user deletes your app on May 10, 2023 and then reinstalls the app on May 15, 2023, and grants read permissions. The earliest date your app can now read data from by default is April 15, 2023.
Handle exceptions
Health Connect throws standard exceptions for CRUD operations when an issue is encountered. Your app should catch and handle each of these exceptions as appropriate.
Each method on HealthConnectClient lists the exceptions that can be thrown.
In general, your app should handle the following exceptions:
| Exception | Description | Recommended best practice |
|---|---|---|
IllegalStateException
| One of the following scenarios has occurred:
| Handle possible issues with the inputs first before doing a request. Preferably, assign values to variables or use them as parameters within a custom function instead of using them directly in your requests so that you can apply error handling strategies. |
IOException
| There are issues encountered upon reading and writing data from disk. | To avoid this issue, here are some suggestions:
|
RemoteException
| Errors have occurred within, or in communicating
with, the underlying service to which the SDK connects. For example, your app is trying to delete a record with a given uid. However, the exception
is thrown after the app finds out upon checking in the underlying service that
the record doesn't exist.
| To avoid this issue, here are some suggestions:
|
SecurityException
| There are issues encountered when the requests require permissions that aren't granted. | To avoid this, make sure that you've declared use of Health Connect data types for your published app. Also, you must declare Health Connect permissions in the manifest file and in your activity. |