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

Once you have constructed the logic, consider following the best practices when writing or updating data.