Most apps that integrate with Health Connect have their own datastore. Health Connect provides ways to keep your app's datastore in sync with Health Connect's datastore.
Your app's datastore should be the source of truth, and should stay in sync with data in Health Connect.
Make sure your app does the following:
- Feed new or updated data from your app's datastore to Health Connect.
- Consume new or updated data from Health Connect.
- Delete data from Health Connect when it is deleted in your app's datastore.
In each case, ensure that the feeding or ingestion process keeps both Health Connect and your app's datastore in sync.
Metadata in Health Connect Records
To help with each of the above, it is first worth examining the Metadata
class. On creation, each Record
in Health Connect has a metadata
field. The following properties are relevant to synchronization:
Properties | |
---|---|
String | id Every `Record` in Health Connect has a unique `id` value. This is automatically assigned when inserting a new record |
Instant | lastModifiedTime Every `Record` also keeps track of the last time the record was modified. This is automatically populated. |
String? | clientRecordId Each `Record` can have a unique ID associated with it to serve as a key back into your app’s datastore. This value is controlled by your app. |
Long | clientRecordVersion Where a record has `clientRecordId`, the `clientRecordVersion` is used to allow for data to stay in sync with the versioning in your app’s datastore. This value is controlled by your app. |
Feeding data to Health Connect from your app's datastore
Preparation
Entries in your app's own datastore should have the following elements:
- A unique key, such as a
UUID
. - A version or timestamp.
Design your app datastore to keep track of what data has already been fed to Health Connect. To achieve this, you can try the following:
- Provide a change log and a token that can be used to retrieve only records since that token.
- Track the last modified time of exported data.
This is essential to ensure that only new or updated data is fed to Health Connect.
Feeding data to Health Connect
- Obtain the list of new or updated entries from your app's datastore.
- For each entry, create a
Record
object of the appropriate type, such as WeightRecord. - Specify a
Metadata
object with eachRecord
using the unique key and version details from your app's own datastore:
val record = WeightRecord(
metadata = Metadata(
clientRecordId = "<Record unique ID from app>",
clientRecordVersion = 12345678 // record version from app, could be a timestamp.
),
weight = ...
)
val record = WeightRecord( metadata = Metadata( clientRecordId = "
- For both new or updated entries from your app's datastore, use
insertRecords()
to feed the data to Health Connect.
Insert vs update
Using the above approach, Health Connect checks whether any existing records in
Health Connect already have the given clientRecordId
- If Yes, then Health Connect compares the value of
clientRecordVersion
, and theRecord
in Health Connect is updated if the newclientRecordVersion
value is greater. - If No, then the data is inserted as a new
Record
.
This approach allows the developer to ensure that records in your app’s own datastore are represented only once in Health Connect, while still remaining updatable.
Practical considerations for writing
- Apps should only write own-sourced data to Health Connect: If data in your app has been imported from another app, then it should be the responsibility of that other app to write its own data to Health Connect.
- Where you are writing a lot of data, perform the writes in batches, for example 1000 records at a time.
- Ensure there is logic in place to handle write exceptions, for example data
that is outside of bounds, or an internal system error:
WorkManager
offers backoff and retry strategies.- If writing to Health Connect is ultimately unsuccessful, ensure that your app can move past that point of export.
- Log and report errors to aid diagnosis.
Consuming data from Health Connect
Health Connect offers the Changes Sync API to allow apps to consume new or updated data from Health Connect, or to be notified of data that has been deleted in Health Connect.
App datastore structure
Your app’s own datastore needs to store the Health Connect id
for each
consumed record: This allows your app to determine whether each incoming change
requires a new record to be created, or whether an existing record in your app’s
own datastore should be updated.
You can use the Changes Sync API without storing the id
in your datastore, but
this results in duplicate records in your app when records are updated in Health
Connect.
Registration
To receive a list of changes to consume into your apps datastore, your app is required to keep track of a Changes Token.
Supplying this Changes Token to Health Connect returns both a list of changes, and also a new Changes Token, for use next time.
To obtain a Changes Token, call getChangesToken
, supplying the required data
types:
val changesToken = healthConnectClient.getChangesToken( ChangesTokenRequest(recordTypes = setOf(StepsRecord::class)) )
Obtaining changes
Call
getChanges()
using the token stored by your app, to obtain a list of changes - if any - that are available:suspend fun processChanges(token: String): String { var nextChangesToken = token do { val response = healthConnectClient.getChanges(nextChangesToken) response.changes.forEach { change -> when (change) { is UpsertionChange -> processUpsertionChange(change) is DeletionChange -> processDeletionChange(change) } } nextChangesToken = response.nextChangesToken } while (response.hasMore) // Return and store the changes token for use next time. return nextChangesToken }
Filter the resulting changes, using the dataOrigin field of the metadata associated with each record, to remove any changes from the calling app. This is important to ensure you are not re-importing data that you have just written.
val filteredChanges = changesResponse.changes.filter { change -> (change is UpsertionChange && change.record.metadata.dataOrigin.packageName != context.packageName) || change is DeletionChange }.toList()
Write any changes to your local datastore
- The
metadata
field on eachRecord
contains both aid
that uniquely identifies this data in Health Connect and alastModifiedTime
. You should use this to determine whether to insert a record into your app’s datastore, or update an existing one. To do this, each entry in your app’s datastore needs to be able to store the Health Connectid
.
- The
Use
ChangesResponse.hasMore
to check whether there are more changes to retrieve.If there are, use
getChanges()
withnextChangeToken
, and repeat.If there are no more changes, store
nextChangeToken
for the next time an import is run.
Practical considerations
- Token expiry - The Changes Token can expire if it is not used within a
90 days, so your app must have a strategy for how to sync data in that case.
- One approach could be to do the following:
- Search your app's datastore for the most recently consumed record that
also has an
id
from Health Connect. - Request records from Health Connect from this timestamp forward, and insert/update in your app's own datastore.
- Request a Changes Token for use next time.
- Search your app's datastore for the most recently consumed record that
also has an
- One approach could be to do the following:
- Foreground reads - Apps can only read data from Health Connect while they are in the Foreground. Whenn syncing data from Health Connect, be aware that access to Health Connect may be interrupted at any point. For example, if reading a large amount of data from Health Connect, your app should be able to handle an interruption midway through this sync, and continue it next time your app is opened.
- Data type change tokens - If an app can consume 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 always either consumed together or not at all.
- Import timings - As your app cannot be notified of new data, it should check for
new data at two points:
- Each time your app becomes active in the foreground, using lifecycle events.
- Periodically if your app remains in the foreground.
- Where new data is available, this should be indicated to the user, allowing them to update the screen if they wish.
Deleting data from Health Connect
When a user deletes their own data from your app, you should ensure that this
data is also removed from Health Connect. Use deleteRecords
to do this.
This takes a list of clientRecordIds
, which makes it convenient to derive
the list of records that need deletion.