データを同期する

このガイドは、ヘルスコネクトのバージョン 1.1.0-alpha12 に対応しています。

ヘルスコネクトと統合されるアプリのほとんどには、信頼できる情報源として機能する独自のデータストアがあります。ヘルスコネクトは、アプリを同期させる方法を提供します。

アプリのアーキテクチャによっては、同期プロセスで次のアクションの一部またはすべてが必要になる場合があります。

  • 新規データまたは更新データをアプリのデータストアからヘルスコネクトにフィードする。
  • ヘルスコネクトからアプリのデータストアにデータ変更を取得する。
  • アプリのデータストアでデータが削除された場合、ヘルスコネクトからデータを削除する。

いずれの場合も、同期プロセスではヘルスコネクトとアプリのデータストアの整合性が保たれている必要があります。

ヘルスコネクトにデータをフィードする

同期プロセスの最初の手順は、アプリのデータストアからヘルスコネクト データストアにデータをフィードすることです。

データを準備する

通常、アプリのデータストア内のレコードには、次の詳細情報が含まれます。

  • 一意のキー(UUID など)。
  • バージョンまたはタイムスタンプ。

ヘルスコネクトにデータを同期するときは、前回の同期以降に挿入、更新、削除されたデータのみを特定してフィードします。

ヘルスコネクトへのデータの書き込み

ヘルスコネクトにデータをフィードする手順は次のとおりです。

  1. アプリのデータストアから、新規、更新、削除されたエントリのリストを取得します。
  2. エントリごとに、そのデータ型に適した Record オブジェクトを作成します。 たとえば、体重に関するデータには WeightRecord オブジェクトを作成します。
  3. RecordMetadata オブジェクトを指定します。これには clientRecordId が含まれます。これは、レコードを一意に識別するために使用できるアプリのデータストアの ID です。これには既存の一意のキーを使用できます。データがバージョニングされている場合は、データのバージョニングと一致する clientRecordVersion も指定します。 バージョニングされていない場合は、代わりに現在のタイムスタンプの Long 値を使用できます。

    val recordVersion = 0L
    // Specify as needed
    // The clientRecordId is an ID that you choose for your record. This
    // is often the same ID you use in your app's datastore.
    val clientRecordId = "<your-record-id>"
    
    val record = WeightRecord(
        metadata = Metadata(
            clientRecordId = clientRecordId,
            clientRecordVersion = recordVersion,
            device = Device(type = Device.TYPE_SCALE)
        ),
        weight = Mass.kilograms(62.0),
        time = Instant.now(),
        zoneOffset = ZoneOffset.UTC,
    )
    healthConnectClient.insertRecords(listOf(record))

  4. Upsert を使用して、ヘルスコネクトにデータを upsert します insertRecords。データを upsert すると、clientRecordId 値がヘルスコネクト データストアに存在し、clientRecordVersion が既存の値より大きい場合、ヘルスコネクトの既存のデータが上書きされます。それ以外の場合、upsert されたデータは新しいデータとして書き込まれます。

    healthConnectClient.insertRecords(arrayListOf(record))

データのフィードについての実践的な考慮事項については、データの書き込みのベスト プラクティスをご覧ください。

ヘルスコネクト ID を保存する

アプリがヘルスコネクトからデータを読み取る場合は、レコードを upsert した後に、レコードのヘルスコネクト id を保存します。ヘルスコネクトからデータ変更を取得するときに削除を処理するには、この id が必要です。

insertRecords 関数は、 InsertRecordsResponse 値のリストを含む id を返します。 レスポンスを使用してレコード ID を取得し、保存します。

val response = healthConnectClient.insertRecords(listOf(record))
for (recordId in response.recordIdsList) {
    // Store recordId to your app's datastore
}

ヘルスコネクトからデータを取得する

同期プロセスの手順の 2 番目では、ヘルスコネクトからアプリのデータストアへのデータ変更を取得します。データ変更には、更新と削除が含まれます。

変更トークンを取得する

ヘルスコネクトから取得する変更のリストを取得するには、アプリで変更トークンを追跡する必要があります。これらは、変更をリクエストして、データ変更のリストと、次回使用される変更トークンの両方を返すときに使用できます。

変更トークンを取得するには、getChangesToken を呼び出して、必要なデータ型を指定します。

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

データ変更の確認

変更トークンを取得すると、そのトークンを使用してすべての変更を取得できます。すべての変更を取得するループを作成し、利用可能なデータ変更があるかどうかを確認することをおすすめします。手順は次のとおりです。

  1. トークンを使用して getChanges を呼び出し、変更のリストを取得します。
  2. 変更の種類が UpsertionChangeDeletionChange かを確認し、必要な操作を行います。
    • UpsertionChange では、データを再インポートしないように、呼び出し元アプリからのものではない変更のみを適用します。
  3. 次の変更トークンを新しいトークンとして割り当てます。
  4. 変更がなくなるまで、手順 1~3 を繰り返します。
  5. 次のトークンを保存し、将来のインポート用に予約します。

suspend fun processChanges(context: Context, 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
}

データを取得するための実践的な考慮事項については、データの同期に関するベスト プラクティスをご覧ください。

データ変更を処理する

アプリのデータストアへの変更を反映します。UpsertionChange については、idlastModifiedTime を使用してレコードを upsert します。 metadataDeletionChange については、提供される id を使用してレコードを 削除 します。 これを行うには、ヘルスコネクト ID を保存する で説明されているように、レコード idを保存しておく必要があります。

ヘルスコネクトからデータを削除する

ユーザーがアプリから自分のデータを削除するときは、ヘルスコネクトからもそのデータが削除されるようにしてください。そのために、deleteRecords を使用します。これにより、レコードタイプと id 値と clientRecordId 値のリストが取得できるので、複数のデータを一括で削除するのに便利です。 を受け取る別の deleteRecords も使用できます。timeRangeFilter

ウェアラブルからの低レイテンシ同期

ウェアラブル フィットネス デバイスからヘルスコネクトに低レイテンシでデータを同期するには、 CompanionDeviceService を使用します。この方法は、BLE GATT 通知または表示をサポートし、Android 8.0(API レベル 26)以降を対象とするデバイスで機能します。CompanionDeviceService を使用すると、アプリが実行されていない場合でも、ウェアラブルからデータを受信してヘルスコネクトに書き込むことができます。BLE のベスト プラクティスの詳細については、 Bluetooth Low Energy の概要をご覧ください。

デバイスを関連付ける

まず、アプリでウェアラブルをアプリに関連付ける 1 回限りのプロセスをユーザーに案内する必要があります。CompanionDeviceManagerこれにより、アプリはデバイスと通信するために必要な権限を取得します。詳細については、 コンパニオン デバイスのペア設定をご覧ください。

マニフェストでサービスを宣言する

次に、アプリのマニフェスト ファイルで CompanionDeviceService を宣言します。AndroidManifest.xml に以下を追加します。

<manifest ...>
   <application ...>
       <service
           android:name=".MyWearableService"
           android:exported="true"
           android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
           <intent-filter>
               <action android:name="android.companion.CompanionDeviceService" />
           </intent-filter>
       </service>
   </application>
</manifest>

CompanionDeviceService を作成する

最後に、CompanionDeviceService を拡張するクラスを作成します。このサービスは、ウェアラブル デバイスへの接続を処理し、BLE GATT コールバックを通じてデータを受信します。新しいデータを受信すると、すぐにヘルスコネクトに書き込まれます。

private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var healthConnectClient: HealthConnectClient? = null
private var bluetoothGatt: BluetoothGatt? = null

override fun onDeviceAppeared(address: String) {
    super.onDeviceAppeared(address)
    healthConnectClient = HealthConnectClient.getOrCreate(this)

    serviceScope.launch {
        val granted = healthConnectClient?.permissionController?.getGrantedPermissions()

        // 1. Check permissions ONCE when the device connects
        if (granted?.contains(HealthPermission.getWritePermission(HeartRateRecord::class)) ?: false) {
            // This is where you'd actually start the Bluetooth connection
            // bluetoothGatt = gattCallback.connect(...)
        }

        // 2. Do your initial database read
        readExerciseSessionAndRoute()
    }
}

private val gattCallback = object : BluetoothGattCallback() {
    override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray
    ) {
        super.onCharacteristicChanged(gatt, characteristic, value)

        // 3. ONLY process the incoming data here
        val rawData = value

        serviceScope.launch {
            // parseWearableData(rawData)
            // insertExerciseRoute() or writeToHealthConnect()
        }
    }
}

データの同期に関するベスト プラクティス

同期プロセスに影響する要因には、次のものがあります。

トークンの有効期限

未使用の変更トークンは 30 日以内に期限切れになるため、そのような場合に情報が失われることのない同期戦略を採用する必要があります。同期戦略には次のようなアプローチがあります。

  • アプリのデータストアで、ヘルスコネクトの id が付与されている、最近使用されたレコードを検索します。
  • 特定のタイムスタンプで始まるレコードをヘルスコネクトにリクエストし、アプリのデータストアで挿入または更新します。
  • 変更トークンをリクエストし、次回必要なときに使用できるように確保します。

推奨されるチェンジ マネジメント戦略

アプリが無効になった場合や変更トークンが期限切れになった場合に備えて、ロジックへの適用方法に応じて次のマネジメント戦略をおすすめします。

  • すべてのデータを読み取って重複を排除する。これが最も理想的な戦略です。
    • 最後にヘルスコネクトからデータを読み込んだときのタイムスタンプを保存します。
    • トークンの有効期限が切れたときに、最新のタイムスタンプまたは過去 30 日間のすべてのデータを再読み取りする。次に、識別子を使用して、以前に読み取ったデータとの重複を排除する。
    • データの更新にはクライアント ID が必要なため、できる限りクライアント ID を実装する。
  • 前回の読み取りのタイムスタンプ以降のデータのみを読み取る。その結果、変更トークンの有効期限が切れるタイミングでデータの不一致が発生するものの、その期間は数時間から数日の短い期間になります。
    • 最後にヘルスコネクトからデータを読み込んだときのタイムスタンプを保存します。
    • トークンの期限が切れたときに、このタイムスタンプ以降のすべてのデータを読み取ります。
  • データを削除して、過去 30 日間のデータを読み取る。これは最初の統合で行われることと非常に似ています。
    • 過去 30 日間にヘルスコネクトからアプリが読み取ったデータをすべて削除します。
    • 削除後にこのデータをすべて再度読み取ります。
  • 重複を排除することなく過去 30 日間のデータを読み取る。これは最もおすすめしない方法で、ユーザーに重複するデータが表示されることになります。
    • 過去 30 日間にヘルスコネクトからアプリが読み取ったデータをすべて削除します。
    • 重複しているエントリを許可します。

データ型の変更トークン

アプリが複数のデータ型を個別に使用する場合は、データ型ごとに個別の変更トークンを使用します。Changes Sync API で複数のデータ型のリストを使用できるのは、それらのデータ型が一緒に使用される場合、またはいずれも使用されない場合のみです。

フォアグラウンドでの読み取り

アプリがフォアグラウンドにある間のみ、ヘルスコネクトからデータを読み取れます。ヘルスコネクトからデータを同期するときに、ヘルスコネクトへのアクセスが予期せず中断される場合があります。たとえば、ヘルスコネクトから大量のデータを読み取るときの同期途中の中断に対応し、次回アプリが開かれたときに続行する必要があります。

バックグラウンドでの読み取り

アプリをバックグラウンドで実行して、ヘルスコネクトからデータを読み取るようにリクエストできます。Background Read 権限をリクエストすると、ユーザーはアプリに バックグラウンドでデータを読み取る権限を付与できます。

インポートのタイミング

アプリは新規データについて通知されないため、次の 2 つのタイミングで新規データがあるかどうかを確認する必要があります。

  • アプリがフォアグラウンドでアクティブになったときに確認します。この場合は、ライフサイクル イベントを使用します。
  • アプリがフォアグラウンドにある間は定期的に確認します。新しいデータが利用可能になったらユーザーに通知して、画面を更新して変更を反映できるようにします。