Synchronize data

Most apps that integrate with Health Connect have their own datastore that serves as the source of truth. Health Connect provides ways to keep your app in sync.

Make sure your app does the following:

  • Feeds new or updated data from your app's datastore to Health Connect.
  • Pulls data changes from Health Connect, which are reflected in your app's datastore.
  • Deletes data from Health Connect when it's deleted in your app's datastore.

In each case, make sure that the syncing process keeps both Health Connect and your app's datastore aligned.

Feed data to Health Connect

The first part of the syncing process is to feed data from your app's datastore to the Health Connect datastore.

Prepare your data

Usually, records in your app's datastore have the following details:

  • A unique key, such as a UUID.
  • A version or timestamp.

Design your app's datastore to keep track of what data has already been fed to Health Connect. To achieve this, apply the following logic:

  • Provide a list of changes and a token that can be used to retrieve records that have updates since the last token was issued.
  • Track the last time the exported data was modified.

These steps are essential to ensure that only new or updated data is fed to Health Connect.

Write data to Health Connect

To feed data into Health Connect, carry out the following steps:

  1. Obtain a list of new or updated entries from your app's datastore.
  2. For each entry, create a Record object appropriate for that data type. For example, create a WeightRecord object for data related to weight.
  3. Specify a Metadata object with each Record using the unique key and version details from your app's datastore. If your data is not versioned, you can use the Long value of the current timestamp as an alternative.

    val record = WeightRecord(
        metadata = Metadata(
            clientRecordId = "<Your record's Client ID>",
            clientRecordVersion = <Your record's version>
        ),
        weight = weight,
        time = time,
        zoneOffset = zoneOffset
    )
    
  4. Upsert data to Health Connect using insertRecords. Upserting data means that any existing data in Health Connect gets overwritten as long as the clientRecordId values exist in the Health Connect datastore, and the clientRecordVersion is higher than the existing value. Otherwise, the upserted data is written as new data.

    healthConnectClient.insertRecords(arrayListOf(record))
    

To learn about the practical considerations for feeding data, check out the best practices for Write data.

Store Health Connect IDs

After upserting your records to Health Connect, your app's datastore needs to store the Health Connect id for each record. This allows your app to check if each incoming change requires creating a new record, or updating an existing record, after you pull the data.

The insertRecords function returns a InsertRecordsResponse that contains the list of id values. Use the response to get the Record IDs and store them.

val response = healthConnectClient.insertRecords(arrayListOf(record))

for (recordId in response.recordIdsList) {
    // Store recordId to your app's datastore
}

Pull data from Health Connect

The second part of the syncing process is to pull for any data changes from Health Connect to your app's datastore. The data changes can include updates and deletions.

Get a Changes token

To get a list of changes to pull from Health Connect, your app needs to keep track of Changes tokens. You can use them when requesting Changes to return both a list of data changes, and a new Changes token to be used next time.

To obtain a Changes token, call getChangesToken and supply the required data types.

val changesToken = healthConnectClient.getChangesToken(
    ChangesTokenRequest(recordTypes = setOf(WeightRecord::class))
)

Check for data changes

Now that you've obtained a Changes token, use it to get all Changes. We recommend creating a loop to get through all the Changes where it checks if there are available data changes. Here are the following steps:

  1. Call getChanges using the token to obtain a list of Changes.
  2. Check each change whether its type of change is an UpsertionChange or a DeletionChange, and perform the necessary operations.
    • For UpsertionChange, only take changes that didn't come from the calling app to make sure you're not re-importing data.
  3. Assign the next Changes token as your new token.
  4. Repeat Steps 1-3 until there are no Changes left.
  5. Store the next token and reserve it for a future import.
suspend fun processChanges(token: String): String {
    var nextChangesToken = token
    do {
        val response = healthConnectClient.getChanges(nextChangesToken)
        response.changes.forEach { change ->
            when (change) {
                is UpsertionChange ->
                    if (change.record.metadata.dataOrigin.packageName != context.packageName) {
                        processUpsertionChange(change)
                    }
                is DeletionChange -> processDeletionChange(change)
            }
        }
        nextChangesToken = response.nextChangesToken
    } while (response.hasMore)
    // Return and store the changes token for use next time.
    return nextChangesToken
}

To learn about the practical considerations for pulling data, check out the best practices for Sync data.

Process data changes

Reflect the changes to your app's datastore. For UpsertionChange, use the id and the lastModifiedTime from its metadata to upsert the record. For DeletionChange, use the id provided to delete the record.

Delete data from Health Connect

When a user deletes their own data from your app, make sure that the data is also removed from Health Connect. Use deleteRecords to do this. This takes a record type and list of id and clientRecordId values, which makes it convenient to batch multiple data for deletion. An alternative deleteRecords that takes in a timeRangeFilter is also available.

Best practices for syncing data

The following factors affect the syncing process.

Token expiration

Since an unused Changes token expires within 30 days, you must use a sync strategy that avoids losing information in such a case. Your strategy could include the following approaches:

  • Search your app datastore for the most recently consumed record that also has an id from Health Connect.
  • Request records from Health Connect that begin with a specific timestamp, and then insert or update them in your app's datastore.
  • Request a Changes token to reserve it for the next time it's needed.

Recommended Changes management strategies

In case your app is getting invalid or expired Changes tokens, we recommend the following management strategies depending on its application in your logic:

  • Read and dedupe all data. This is the most ideal strategy.
    • Store the timestamp of the last time they read data from Health Connect.
    • On token expiry, re-read all data from the most recent timestamp or for the last 30 days. Then, dedupe it against the previously read data using identifiers.
    • Ideally, implement Client IDs since they are required for data updates.
  • Only read data since the last read timestamp. This results in some data discrepancies around the time of Changes token expiry, but the time period is shorter that could take a few hours to a couple of days.
    • Store the timestamp of the last time they read data from Health Connect.
    • On token expiry, read all data from this timestamp onwards.
  • Delete then read data for the last 30 days. This aligns more closely with what happens on the first integration.
    • Delete all data read by the app from Health Connect for the last 30 days.
    • Once deleted, read all of this data again.
  • Read data for last 30 days without deduping. This is the least ideal strategy, and results in having duplicate data displayed to users.
    • Delete all data read by the app from Health Connect for the last 30 days.
    • Allow duplicate entries.

Data type Changes tokens

If your app consumes more than one data type independently, use separate Changes Tokens for each data type. Only use a list of multiple data types with the Changes Sync API if these data types are either consumed together or not at all.

Foreground reads

Apps can only read data from Health Connect while they are in the foreground. When syncing data from Health Connect, access to Health Connect may be interrupted at any point. For example, your app must handle interruptions midway through a sync when reading a large amount of data from Health Connect, and continue the next time the app is opened.

Import timings

As your app can't get notified of new data, check for new data at two points:

  • Each time your app becomes active in the foreground. In this case, use lifecycle events.
  • Periodically, while your app remains in the foreground. Notify users when new data is available, allowing them to update their screen to reflect the changes.