Write data

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:

  1. Frequency of writes: how often your application pushes any new data into Health Connect. For example, write new data every 15 minutes.
  2. 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%