This guide covers the process of writing or updating data in Health Connect.
Set up data structure
Before writing data, we need to set up the records first. For more than 50 data types, each have their respective structures. See the Jetpack reference for more details about the data types available.
Basic records
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.
The following example shows how to set steps count data:
val stepsRecord = StepsRecord(
count = 120,
startTime = START_TIME,
endTime = END_TIME,
startZoneOffset = START_ZONE_OFFSET,
endZoneOffset = END_ZONE_OFFSET
)
Records with units of measurement
Health Connect can store values along with their units of measurement to provide accuracy. One example is the Nutrition data type that is vast and comprehensive. It includes a wide variety of optional nutrient fields ranging from total carbohydrates to vitamins. Each data point represents the nutrients that were potentially consumed as part of a meal or food item.
In this data type, all of the nutrients are represented in units of
Mass
, while energy
is represented in a unit of Energy
.
The following example shows how to set nutrition data for a user who has eaten a banana:
val banana = NutritionRecord(
name = "banana",
energy = 105.0.kilocalories,
dietaryFiber = 3.1.grams,
potassium = 0.422.grams,
totalCarbohydrate = 27.0.grams,
totalFat = 0.4.grams,
saturatedFat = 0.1.grams,
sodium = 0.001.grams,
sugar = 14.0.grams,
vitaminB6 = 0.0005.grams,
vitaminC = 0.0103.grams,
startTime = START_TIME,
endTime = END_TIME,
startZoneOffset = START_ZONE_OFFSET,
endZoneOffset = END_ZONE_OFFSET
)
Records with series data
Health Connect can store a list of series data. One example is the Heart Rate data type that captures a series of heartbeat samples detected between readings.
In this data type, the parameter samples
is represented by a list of
Heart Rate samples. Each sample contains a beatsPerMinute
value and a time
value.
The following example shows how to set heart rate series data:
val heartRateRecord = HeartRateRecord(
startTime = START_TIME,
startZoneOffset = START_ZONE_OFFSET,
endTime = END_TIME,
endZoneOffset = END_ZONE_OFFSET,
// records 10 arbitrary data, to replace with actual data
samples = List(10) { index ->
HeartRateRecord.Sample(
time = START_TIME + Duration.ofSeconds(index.toLong()),
beatsPerMinute = 100 + index.toLong(),
)
}
)
Write data
One of the common workflows in Health Connect is writing data. To add records,
use insertRecords
.
The following example shows how to write data inserting step counts:
suspend fun insertSteps(healthConnectClient: HealthConnectClient) {
try {
val stepsRecord = StepsRecord(
count = 120,
startTime = START_TIME,
endTime = END_TIME,
startZoneOffset = START_ZONE_OFFSET,
endZoneOffset = END_ZONE_OFFSET
)
healthConnectClient.insertRecords(listOf(stepsRecord))
} catch (e: Exception) {
// Run error handling here
}
}
Update data
If you need to change one or more records, especially when you need to sync your app datastore with data from Health Connect, you can update your data. There are two ways to update existing data which depends on the identifier used to find records.
Metadata
It is worth examining the Metadata
class first as this is necessary when
updating data. On creation, each Record
in Health Connect has a
metadata
field. The following properties are relevant to
synchronization:
Properties | Description |
---|---|
id
|
Every Record in Health Connect has a unique id
value.Health Connect automatically populates this when inserting a new record. |
lastModifiedTime
|
Every Record also keeps track of the last time the
record was modified.Health Connect automatically populates this. |
clientRecordId
|
Each Record can have a unique ID associated with
it to serve as reference in your app datastore.
Your app supplies this value. |
clientRecordVersion
|
Where a record has clientRecordId , the
clientRecordVersion can be used to allow data to
stay in sync with the version in your app
datastore.Your app supplies this value. |
Update through Record ID
To update data, prepare the needed records first. Perform any changes to the
records if necessary. Then, call updateRecords
to make
the changes.
The following example shows how to update data. For this purpose, each record has its zone offset values adjusted into PST.
suspend fun updateSteps(
healthConnectClient: HealthConnectClient,
prevRecordStartTime: Instant,
prevRecordEndTime: Instant
) {
try {
val request = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = StepsRecord::class,
timeRangeFilter = TimeRangeFilter.between(
prevRecordStartTime,
prevRecordEndTime
)
)
)
val newStepsRecords = arrayListOf<StepsRecord>()
for (record in request.records) {
// Adjusted both offset values to reflect changes
val sr = StepsRecord(
count = record.count,
startTime = record.startTime,
startZoneOffset = record.startTime.atZone(ZoneId.of("PST")).offset,
endTime = record.endTime,
endZoneOffset = record.endTime.atZone(ZoneId.of("PST")).offset,
metadata = record.metadata
)
newStepsRecords.add(sr)
}
client.updateRecords(newStepsRecords)
} catch (e: Exception) {
// Run error handling here
}
}
Upsert through Client Record ID
If you are using the optional Client Record ID and Client Record Version values,
we recommend using insertRecords
instead of updateRecords
.
The insertRecords
function has the ability to upsert data.
If the data exists in Health Connect based on the given set of
Client Record IDs, it gets overwritten. Otherwise, it is written as new data.
This scenario is useful whenever you need to sync data from
your app datastore to Health Connect.
The following example shows how to perform an upsert on data pulled from the app datastore:
suspend fun pullStepsFromDatastore() : ArrayList<StepsRecord> {
val appStepsRecords = arrayListOf<StepsRecord>()
// Pull data from app datastore
// ...
// Make changes to data if necessary
// ...
// Store data in appStepsRecords
// ...
var sr = StepsRecord(
// Assign parameters for this record
metadata = Metadata(
clientRecordId = cid
)
)
appStepsRecords.add(sr)
// ...
return appStepsRecords
}
suspend fun upsertSteps(
healthConnectClient: HealthConnectClient,
newStepsRecords: ArrayList<StepsRecord>
) {
try {
healthConnectClient.insertRecords(newStepsRecords)
} catch (e: Exception) {
// Run error handling here
}
}
After that, you can call these functions in your main thread.
upsertSteps(healthConnectClient, pullStepsFromDatastore())
Value check in Client Record Version
If your process of upserting data includes the Client Record Version, Health
Connect performs comparison checks in the clientRecordVersion
values. If the version from the inserted data is higher than the
version from the existing data, the upsert happens. Otherwise, the process
ignores the change and the value remains the same.
To include versioning in your data, you need to supply
Metadata.clientRecordVersion
with a Long
value based on your versioning
logic.
val sr = StepsRecord(
count = count,
startTime = startTime,
startZoneOffset = startZoneOffset,
endTime = endTime,
endZoneOffset = endZoneOffset,
metadata = Metadata(
clientRecordId = cid,
clientRecordVersion = version
)
)
Upserts don't automatically increment version
whenever there are changes,
preventing any unexpected instances of overwriting data. With that, you have to
manually supply it with a higher value.
Best practices for writing data
Apps must only write own-sourced data to Health Connect.
If data in your app has been imported from another app, then the responsibility falls onto the other app to write its own data to Health Connect.
It's also a good idea to implement logic that handles write exceptions such as data being outside of bounds, or an internal system error. You can apply your backoff and retry strategies on a job scheduling mechanism. If writing to Health Connect is ultimately unsuccessful, make sure that your app can move past that point of export. Don't forget to log and report errors to aid diagnosis.
When tracking data, there are a couple of suggestions you can follow depending on the way your app writes data.
Passive tracking
This includes apps that perform passive fitness or health tracking, like recording steps or heart rate continuously in the background.
Your app needs to periodically write data into Health Connect in the following ways:
- On every sync, only write new data and updated data that was modified since the last sync.
- Chunk requests to at most 1000 records per write request.
- Use
WorkManager
to schedule periodic background tasks, with a time period of at least 15 minutes. Restrict tasks to run only when the device is idle and is not low on battery.
val constraints = Constraints.Builder() .requiresBatteryNotLow() .requiresDeviceIdle(true) .build() val writeDataWork = PeriodicWorkRequestBuilder<WriteDataToHealthConnectWorker>( 15, TimeUnit.MINUTES, 5, TimeUnit.MINUTES ) .setConstraints(constraints) .build()
Active tracking
This includes apps that perform event-based tracking such as exercise and sleep, or manual user input such as nutrition. These records are created when the app is in the foreground, or in rare events where it is used a few times in a day.
Ensure that your app doesn't keep Health Connect running for the entire duration of the event.
Data must be written into Health Connect in one of two ways:
- Sync data into Health Connect after the event is complete. For example, sync data when the user ends a tracked exercise session.
- Schedule a one-off task using
WorkManager
to sync data later.
Best practices for granularity and frequency of writes
When writing data into Health Connect, use appropriate resolution. Using the appropriate resolution helps reduce storage load, while still maintaining consistent and accurate data. Data resolution encompasses 2 things:
- Frequency of writes: how often your application pushes any new data into Health Connect. For example, write new data every 15 minutes.
- Granularity of written data: how often the data that is pushed in was sampled. For example, write heart rate samples every 5s. Not every data type requires the same sample rate. There is little benefit to updating step count data every second, as opposed to a less frequent cadence such as every 60 seconds. However, higher sample rates may give users a more detailed and granular look at their health and fitness data. Sample rate frequencies should strike a balance between detail and performance.
Write data monitored throughout the day
For data collected on an ongoing basis, like steps, your application should write to Health Connect at least every 15 minutes through the day.
Data type |
Unit |
Expected |
Example |
Steps |
steps |
Every 1 minute |
23:14 - 23:15 - 5 steps 23:16 - 23:17 - 22 steps 23:17 - 23:18 - 8 steps |
StepsCadence |
steps/min |
Every 1 minute |
23:14 - 23:15 - 5 spm 23:16 - 23:17 - 22 spm 23:17 - 23:18 - 8 spm |
Wheelchair pushes |
pushes |
Every 1 minute |
23:14 - 23:15 - 5 pushes 23:16 - 23:17 - 22 pushes 23:17 - 23:18 - 8 pushes |
ActiveCaloriesBurned |
Calories |
Every 15 minutes |
23:15 - 23:30 - 2 Calories 23:30 - 23:45 - 25 Calories 23:45 - 00:00 - 5 Calories |
TotalCaloriesBurned |
Calories |
Every 15 minutes |
23:15 - 23:30 - 16 Calories 23:30 - 23:45 - 16 Calories 23:45 - 00:00 - 16 Calories |
Distance |
km/min |
Every 1 minute |
23:14-23:15 - 0.008 km 23:16 - 23:16 - 0.021 km 23:17 - 23:18 - 0.012 km |
ElevationGained |
m |
Every 1 minute |
20:36 - 20:37 - 3.048m 20:39 - 20:40 - 3.048m 23:23 - 23:24 - 9.144m |
FloorsClimbed |
floors |
Every 1 minute |
23:14 - 23:15 - 5 floors 23:16 - 23:16 - 22 floors 23:17 - 23:18 - 8 floors |
HeartRate |
bpm |
Every 1 minute |
6:11am - 55 bpm |
HeartRateVariabilityRmssd |
ms |
Every 1 minute |
6:11am - 23 ms |
RespiratoryRate |
breaths/minute |
Every 1 minute |
23:14 - 23:15 - 60 breaths/minute 23:16 - 23:16 - 62 breaths/minute 23:17 - 23:18 - 64 breaths/minute |
OxygenSaturation |
% |
Every 1 hour |
6:11 - 95.208% |
Write sessions
Data should be written to Health Connect at the end of the workout or sleep session.
As a best practice, any sleep session or exercise session should be written with
the recording device and the appropriate metadata, including
RecordingMethod
.
At minimum, your application should follow the guidance in the "expected" column below. Where possible, follow the "best" guidance.
Data tracked during an exercise
Data type |
Unit |
Expected |
Best |
Example |
Steps |
steps |
Every 1 minute |
Every 1 second |
23:14-23:15 - 5 steps 23:16 - 23:17 - 22 steps 23:17 - 23:18 - 8 steps |
StepsCadence |
steps/min |
Every 1 minute |
Every 1 second |
23:14-23:15 - 35 spm 23:16 - 23:17 - 37 spm 23:17 - 23:18 - 40 spm |
Wheelchair pushes |
pushes |
Every 1 minute |
Every 1 second |
23:14-23:15 - 5 pushes 23:16 - 23:17 - 22 pushes 23:17 - 23:18 - 8 pushes |
CyclingPedalingCadence |
rpm |
Every 1 minute |
Every 1 second |
23:14-23:15 - 65 rpm 23:16 - 23:17 - 70 rpm 23:17 - 23:18 - 68 rpm |
Power |
watts |
Every 1 minute |
Every 1 second |
23:14-23:15 - 250 watts 23:16 - 23:17 - 255 watts 23:17 - 23:18 - 245 watts |
Speed |
km/min |
Every 1 minute |
Every 1 second |
23:14-23:15 - 0.3 km/min 23:16 - 23:17 - 0.4 km/min 23:17 - 23:18 -0.4 km/min |
Distance |
km/m |
Every 1 minute |
Every 1 second |
23:14-23:15 - 0.008 km 23:16 - 23:16 - 0.021 km 23:17 - 23:18 - 0.012 km |
ActiveCaloriesBurned |
Calories |
Every 1 minute |
Every 1 second |
23:14-23:15 - 20 Calories 23:16 - 23:17 - 20 Calories 23:17 - 23:18 - 25 Calories |
TotalCaloriesBurned |
Calories |
Every 1 minute |
Every 1 second |
23:14-23:15 - 36 Calories 23:16 - 23:17 - 36 Calories 23:17 - 23:18 - 41 Calories |
ElevationGained |
m |
Every 1 minute |
Every 1 second |
20:36 - 20:37 - 3.048m 20:39 - 20:40 - 3.048m 23:23 - 23:24 - 9.144m |
ExerciseRoutes |
lat/lng/alt |
Every 3-5 seconds |
Every 1 second |
|
HeartRate |
bpm |
Every 1 minute |
Every 1 second |
23:14-23:15 - 150 bpm 23:16 - 23:17 -152 bpm 23:17 - 23:18 - 155 bpm |
Data tracked during sleep
Data type |
Unit |
Expected samples |
Example |
Sleep Staging |
stage |
Granular period of time per sleep stage |
23:46 - 23:50 - awake 23:50 - 23:56 - light sleep 23:56 - 00:16 - deep sleep |
RestingHeartRate |
bpm |
Single daily value (expected first thing in the morning) |
6:11am - 60 bpm |
OxygenSaturation |
% |
Single daily value (expected first thing in the morning) |
6:11 - 95.208% |