データを書き込む

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

このガイドでは、ヘルスコネクトでデータの書き込みまたは更新を行うプロセスについて説明します。

ゼロ値を処理する

歩数、距離、消費カロリーなどの一部のデータ型では、値が 0 になることがあります。ユーザーがデバイスを装着している間に実際に活動していなかったことを反映している場合にのみ、ゼロ値を書き込みます。デバイスを装着していなかった場合、データが欠落している場合、バッテリーが切れた場合は、ゼロ値を書き込まないでください。このような場合は、誤解を招くデータを避けるために、レコードを省略します。

データ構造を設定する

データを書き込む前に、まずレコードを設定する必要があります。50 を超えるデータ型があり、各データ型にそれぞれの構造があります。使用可能なデータ型について詳しくは、Jetpack リファレンスをご覧ください。

基本のレコード

ヘルスコネクトの Steps データ型には、各読み取りの間にユーザーが歩いた歩数が記録されます。歩数は、健康、フィットネス、ウェルネスのプラットフォームで共通の測定値を表します。

次の例は、歩数データを設定する方法を示しています。

val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime)
val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = zoneOffset,
    endZoneOffset = zoneOffset,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)
healthConnectClient.insertRecords(listOf(stepsRecord))

測定単位を含むレコード

ヘルスコネクトでは、精度を高めるため、測定単位とともに値を格納できます。その一例が、広範囲を包括的にカバーする Nutrition データ型です。総炭水化物からビタミンまで、さまざまな栄養素項目が対象に含まれます。各データポイントは、食事または食品の一部として摂取された可能性のある栄養素を表します。

このデータ型では、すべての栄養素が Mass の単位で表現され、energy は Energy の単位で表現されます。

次の例は、バナナを食べたユーザーの栄養データを設定する方法を示しています。

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(1))

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 = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.TYPE_PHONE)
    )
)

系列データを含むレコード

ヘルスコネクトでは系列データのリストを格納できます。その一例が、読み取り間に検出された一連の心拍数のサンプルをキャプチャする Heart Rate データ型です。

このデータ型では、パラメータ samples は心拍数サンプルのリストで表されます。各サンプルには、beatsPerMinute 値と time 値が含まれています。

次の例は、心拍数の系列データを設定する方法を示しています。

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(5))

val heartRateRecord = HeartRateRecord(
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    // records 10 arbitrary data, to replace with actual data
    samples = List(10) { index ->
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(index.toLong()),
            beatsPerMinute = 100 + index.toLong(),
        )
    },
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ))

ユーザーに権限をリクエストする

クライアント インスタンスを作成した後、アプリはユーザーに権限をリクエストする必要があります。ユーザーがいつでも権限を付与または拒否できるようにする必要があります。そのためには、必要なデータ型の権限セットを作成します。まず、セット内の権限が Android マニフェストで宣言されていることを確認します。

val permissions =
    setOf(
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class),
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class)
    )
getGrantedPermissions を使用して、アプリに必要な権限がすでに付与されているかどうかを確認します。持っていない場合は、createRequestPermissionResultContract を使用して権限をリクエストします。ヘルスコネクトの権限画面が表示されます。
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
ユーザーはいつでも権限を付与または取り消すことができるため、アプリは権限を使用するたびに権限をチェックし、権限が失われた状況に対応できるように設計する必要があります。

データを書き込む

ヘルスコネクトの一般的なワークフローとして、データの書き込みがあります。レコードを追加するには、insertRecords を使用します。

次の例は、歩数を挿入するデータを書き込む方法を示しています。

val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime)
val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = zoneOffset,
    endZoneOffset = zoneOffset,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)
healthConnectClient.insertRecords(listOf(stepsRecord))

データを更新する

1 つ以上のレコードを変更する必要がある場合、特にアプリのデータストアをヘルスコネクトのデータと同期する必要がある場合は、データを更新できます。既存のデータを更新するには、レコードの検索に使用される ID に応じて 2 つの方法があります。

メタデータ

データを更新するときに必要になるため、最初に Metadata クラスを確認することをおすすめします。作成時、ヘルスコネクトの各 Record には metadata フィールドがあります。同期に関連するプロパティは次のとおりです。

プロパティ 説明
id ヘルスコネクトの各 Record には一意の id 値があります。
ヘルスコネクトでは、 新しいレコードを挿入するときに、この値が自動的に設定されます。
lastModifiedTime 各 Record にはレコードの最終更新日時も記録されます。
この値はヘルスコネクトによって自動的に入力されます。
clientRecordId 各 Record に一意の ID を関連付けて、アプリのデータストアで参照として使用することができます。
この値はアプリが指定します。
clientRecordVersion レコードに clientRecordId がある場合は、clientRecordVersion を使用して、データをアプリ データストアのバージョンと同期させることができます。
この値はアプリが指定します。

時間範囲で読み取り後に更新する

データを更新するには、まず必要なレコードを用意します。必要に応じてレコードに変更を加えます。次に、updateRecords を呼び出して変更を行います。

次の例は、データを更新する方法を示しています。この目的のために、各レコードのゾーン オフセット値は 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)
        }

        healthConnectClient.updateRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

クライアント レコード ID を介してアップサートする

省略可能なクライアント レコード ID とクライアント レコード バージョンの値を使用している場合は、updateRecords ではなく insertRecords を使用することをおすすめします。

insertRecords 関数を使用するとデータをアップサートできます。指定されたクライアント レコード ID のセットに基づくデータがヘルスコネクトに存在する場合、データは上書きされます。存在しない場合は、新しいデータとして書き込まれます。このシナリオは、アプリのデータストアからヘルスコネクトにデータを同期する必要がある場合に便利です。

次の例は、アプリのデータストアから取得されたデータに対してアップサートを実行する方法を示しています。

 fun pullStepsFromDatastore(startTime: Instant, endTime: Instant) : ArrayList<StepsRecord> {
    val appStepsRecords = arrayListOf<StepsRecord>()
    // Pull data from app datastore
    // ...
    // Make changes to data if necessary
    // ...
    // Store data in appStepsRecords
    // ...
    var sr = StepsRecord(
        metadata = Metadata.activelyRecorded(
            clientRecordId = "Your client record ID",
            clientRecordVersion = 0L,
            device = Device(type = Device.TYPE_WATCH)
        ),
        startTime = startTime,
        startZoneOffset = startTime.atZone(ZoneId.of("PST")).offset,
        endTime = endTime,
        endZoneOffset = endTime.atZone(ZoneId.of("PST")).offset,
        count = 120
    )
    appStepsRecords.add(sr)
    // ...
    return appStepsRecords
}

suspend fun upsertSteps(
    healthConnectClient: HealthConnectClient,
    newStepsRecords: ArrayList<StepsRecord>
) {
    try {
        healthConnectClient.insertRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

その後、これらの関数をメインのスレッドで呼び出すことができます。

upsertSteps(healthConnectClient, pullStepsFromDatastore(
    startTime = startTime,
    endTime = endTime
))

クライアント レコード バージョンの値のチェック

データをアップサートするプロセスにクライアント レコード バージョンが含まれている場合、ヘルスコネクトは clientRecordVersion 値の比較チェックを行います。挿入されるデータのバージョンが既存のデータのバージョンよりも高い場合は、アップサートが行われます。それ以外の場合は、この変更は無視され、値は同じままになります。

データにバージョニングを含めるには、バージョニングのロジックに基づいて Metadata.clientRecordVersion に Long 値を指定する必要があります。

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 100L,
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.activelyRecorded(
        clientRecordId = "Your supplied record ID",
        clientRecordVersion = 0L, // Your supplied record version
        device = Device(type = Device.TYPE_WATCH)
    )
)

Upsert は、データが予期せず上書きされるのを防ぐため、変更が発生しても version を自動更新しません。そのため、今より大きな値を手動で指定する必要があります。

一般的なガイドライン

アプリは、サポートされているすべてのファーストパーティ データを書き込む必要があります。必要に応じて、サードパーティ ソースから取得したデータをアプリで書き込むように選択できます。ただし、アプリがヘルスコネクトからデータを読み取った場合、そのデータをヘルスコネクトに書き戻してはなりません。

別のソースからインポートまたは派生したデータを書き込む場合は、そのデータの出所とソース デバイスのメタデータを正しく帰属させる必要があります。これを行うには、書き込まれたレコードごとに次のメタデータを指定する必要があります。

  • recordingMethod: 自動または手動で記録されたデータについては、記録されたアクティビティのタイプを反映するように記録方法が更新されることが想定されます。
    • RECORDING_METHOD_AUTOMATICALLY_RECORDED: データが自動的に記録された場合(フィットネス バンドがユーザーのランニングを自動的に検出した場合など)。
    • RECORDING_METHOD_ACTIVELY_RECORDED: ユーザーがウェアラブルでサイクリングなどの新しいアクティビティを開始した場合。
    • RECORDING_METHOD_MANUAL_ENTRY: ユーザーがデータを手動で入力した場合。
  • device.type: サポートされている Device タイプのいずれかからデバイスタイプを指定する必要があります。
  • device.manufacturer: デバイスのメーカー(例: 「Fitbit」)。
  • device.model: デバイスのモデル(「Charge 3」など)。
  • device.udi:(省略可)医療機器の固有識別子(UDI)の機器識別子(DI)部分。必要な権限とプライバシーに関するガイドラインの詳細については、メタデータ ガイドを参照してください。

メタデータを正しく設定することは、データの透明性を確保するうえで重要であり、ユーザーが健康情報の出所を理解するのに役立ちます。詳しくは、ヘルスコネクトのメタデータ ガイドをご覧ください。

アプリのデータが別のアプリからインポートされたものである場合、そのデータをヘルスコネクトに書き込む役割はインポート元のアプリが担います。

また、境界外のデータや内部システムエラーなどの書き込み例外を処理するロジックを実装することもおすすめします。バックオフと再試行の戦略は、ジョブ スケジューリング メカニズムに適用できます。ヘルスコネクトへの書き込みが最終的に失敗した場合でも、アプリは必要な処理を引き続き行える状態でなければなりません。診断に役立てるため、必ずエラーをログに記録して報告してください。

データをトラッキングする場合、アプリがデータを書き込む方法に応じて、いくつかの推奨事項があります。

タイムゾーンの処理

時間ベースのレコードを書き込むときは、オフセットをデフォルトで zoneOffset.UTC に設定しないでください。ユーザーが別のゾーンにいる場合、タイムスタンプが不正確になる可能性があります。代わりに、デバイスの実際の位置に基づいてオフセットを計算します。デバイスのタイムゾーンは、ZoneId.systemDefault() を使用して取得できます。

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofDays(1))
val stepsRecords = mutableListOf<StepsRecord>()
var sampleTime = startTime
val minutesBetweenSamples = 15L
while (sampleTime < endTime) {
    // Get the default ZoneId then convert it to an offset
    val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(sampleTime)
    stepsRecords += StepsRecord(
        startTime = sampleTime.minus(Duration.ofMinutes(minutesBetweenSamples)),
        startZoneOffset = zoneOffset,
        endTime = sampleTime,
        endZoneOffset = zoneOffset,
        count = Random.nextLong(1, 100),
        metadata = Metadata.activelyRecorded(device = Device(type = Device.TYPE_WATCH)),
    )
    sampleTime = sampleTime.plus(Duration.ofMinutes(minutesBetweenSamples))
}
healthConnectClient.insertRecords(
    stepsRecords
)

詳細については、ZoneId のドキュメントをご覧ください。

書き込み頻度と粒度

ヘルスコネクトにデータを書き込むときには、適切な解像度を使用します。適切な解像度を使用すると、ストレージの負荷を軽減しながら、一貫性のある正確なデータを維持できます。データ解決には次の 2 つの要素が含まれます。

  • 書き込みの頻度: アプリケーションが新しいデータをヘルスコネクトに書き込む頻度。
    • 新しいデータが利用可能になったら、デバイスのパフォーマンスに配慮しながら、できるだけ頻繁にデータを書き込みます。
    • バッテリー寿命やその他のパフォーマンスに悪影響を与えないように、書き込み間隔の最大値は 15 分にする必要があります。
  • 書き込まれたデータの粒度: データのサンプリング頻度。
    • たとえば、5 秒ごとに心拍数のサンプルを書き込みます。
    • すべてのデータ型に同じサンプルレートが必要なわけではありません。歩数データを 1 秒ごとに更新してもあまり意味はありません。60 秒程度の少ない頻度で十分です。
    • サンプルレートが高いほど、ユーザーは健康とフィットネスのデータをよりきめ細かく把握できます。サンプルレートの頻度は、詳細とパフォーマンスのバランスをとれるように設定する必要があります。

その他のガイドライン

データを書き込む際は、次のガイドラインに沿ってください。

  • 同期のたびに新しいデータのみを書き込み、前回の同期の後で変更されたデータのみを更新します。
  • 1 回の書き込みリクエストのレコード数を最大 1,000 個にしてリクエストをチャンクします。
  • デバイスがアイドル状態でバッテリー残量が十分にある場合にのみタスクを実行するよう制限します。
  • バックグラウンド タスクの場合、WorkManager を使用して、最大 15 分の期間で定期的なタスクをスケジュール設定します。

次のコードでは、WorkManager を使用して、最大期間 15 分、フレックス間隔 5 分の定期的なバックグラウンド タスクをスケジュール設定しています。この構成は、PeriodicWorkRequest.Builder クラスを使用して設定します。

val constraints = Constraints.Builder()
    .requiresBatteryNotLow()
    .requiresDeviceIdle(true)
    .build()

val writeDataWork = PeriodicWorkRequestBuilder<WriteDataToHealthConnectWorker>(
        15,
        TimeUnit.MINUTES,
        5,
        TimeUnit.MINUTES
    )
    .setConstraints(constraints)
    .build()

アクティブ トラッキング

これには、エクササイズや睡眠などのイベントベースのトラッキングを行うアプリや、栄養などの手動のユーザー入力を行うアプリが含まれます。これらのレコードは、アプリがフォアグラウンドにある場合、または 1 日に数回使用されるまれなイベントの場合に作成されます。

イベント期間全体を通じて、アプリでヘルスコネクトの実行が維持されないようにします。

データは、次の 2 つの方法のいずれかを使って、ヘルスコネクトに書き込む必要があります。

  • イベントの完了後にデータをヘルスコネクトに同期します。たとえば、トラッキング対象のエクササイズ セッションをユーザーが終了したときにデータを同期します。
  • WorkManager を使用して、1 回限りのタスクのデータを後で同期するようにスケジュール設定します。

書き込みの粒度と頻度に関するベスト プラクティス

ヘルスコネクトにデータを書き込むときには、適切な解像度を使用します。適切な解像度を使用すると、ストレージの負荷を軽減しながら、一貫性のある正確なデータを維持できます。データ解決には次の 2 つの要素が含まれます。

  1. 書き込みの頻度: アプリケーションが新しいデータをヘルスコネクトにプッシュする頻度。デバイスのパフォーマンスに配慮しながら、新しいデータが利用可能になったらできるだけ頻繁にデータを書き込みます。バッテリー駆動時間やその他のパフォーマンスに悪影響を与えないように、書き込み間隔の最大値は 15 分にしてください。

  2. 書き込まれたデータの粒度: プッシュされたデータがサンプリングされた頻度。たとえば、5 秒ごとに心拍数のサンプルを書き込みます。すべてのデータ型に同じサンプルレートが必要なわけではありません。歩数データを 1 秒ごとに更新してもあまり意味はありません。60 秒程度の少ない頻度で十分です。一方、サンプルレートが高いほど、ユーザーは健康とフィットネスのデータをよりきめ細かく把握できます。サンプルレートの頻度は、詳細とパフォーマンスのバランスをとれるように設定する必要があります。

系列データのレコードを構造化する

HeartRateRecord など、一連のサンプルを使用するデータ型では、レコードを正しく構造化することが重要です。常に更新される 1 日のレコードを 1 つ作成するのではなく、特定の時間間隔を表す複数の小さなレコードを作成する必要があります。

たとえば、心拍数データの場合、1 分ごとに新しい HeartRateRecord を作成する必要があります。各レコードには、その 1 分間の開始時刻と終了時刻が含まれ、その 1 分間にキャプチャされたすべての心拍数サンプルが含まれます。

ヘルスコネクトとの定期的な同期(15 分ごとなど)の際に、アプリは前回の同期以降に作成された 1 分間のレコードをすべて書き込む必要があります。これにより、レコードのサイズが管理可能な状態に保たれ、データのクエリと処理のパフォーマンスが向上します。

次の例は、複数のサンプルを含む 1 分間の HeartRateRecord を作成する方法を示しています。

val startTime = Instant.now().truncatedTo(ChronoUnit.MINUTES)
val endTime = startTime.plus(Duration.ofMinutes(1))

val heartRateRecord = HeartRateRecord(
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    // Create a new record every minute, containing a list of samples.
    samples = listOf(
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(15),
            beatsPerMinute = 80,
        ),
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(30),
            beatsPerMinute = 82,
        ),
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(45),
            beatsPerMinute = 85,
        )
    ),
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ))

1 日を通してモニタリングされたデータを書き込む

歩数など、継続的に収集されるデータについては、新しいデータが利用可能になったら、できるだけ頻繁にヘルスコネクトに書き込む必要があります。バッテリー寿命やその他のパフォーマンスに悪影響を与えないようにするため、書き込み間隔の最大値は 15 分にする必要があります。

表 1: データの書き込みに関するガイダンス

データの種類

単位

想定される

例

手順

手順

1 分ごと

23:14 - 23:15 - 5 歩

23:16 - 23:17 - 22 歩

23:17 - 23:18 - 8 歩

StepsCadence

歩/分

1 分ごと

23:14 - 23:15 - 5 歩/分

23:16 - 23:17 - 22 歩/分

23:17 - 23:18 - 8 歩/分

車椅子の車輪を押した回数

プッシュ

1 分ごと

23:14 - 23:15 - 5 回のプッシュ

23:16 - 23:17 - 22 回のプッシュ

23:17 - 23:18 - 8 回のプッシュ

ActiveCaloriesBurned

カロリー

15 分ごと

23:15 - 23:30 - 2 カロリー

23:30 ~ 23:45 - 25 カロリー

23:45 - 00:00 - 5 カロリー

TotalCaloriesBurned

カロリー

15 分ごと

23:15 ~ 23:30 - 16 カロリー

23:30 - 23:45 - 16 カロリー

23:45 - 00:00 - 16 カロリー

距離

km/min

1 分ごと

23:14 ~ 23:15 - 0.008 km

23:16 - 23:16 - 0.021 km

23:17 - 23:18 - 0.012 km

ElevationGained

m

1 分ごと

20:36 - 20:37 - 3.048m

20:39 - 20:40 - 3.048m

23:23 - 23:24 - 9.144m

FloorsClimbed

侵入検知、

1 分ごと

23:14 - 23:15 - 5 階

23:16 - 23:16 - 22 階

23:17 - 23:18 - 8 階

HeartRate

bpm

1 分間に 4 回

6:11:15am - 55 bpm

午前 6 時 11 分 30 秒 - 56 bpm

6:11:45 am - 56 bpm

6:12:00 am - 55 bpm

HeartRateVariabilityRmssd

ミリ秒

1 分ごと

午前 6 時 11 分 - 23 ミリ秒

RespiratoryRate

呼吸数/分

1 分ごと

23:14 - 23:15 - 60 回/分

23:16 - 23:16 - 62 回/分

23:17 - 23:18 - 64 回/分

OxygenSaturation

%

1 時間ごと

6:11 - 95.208%

ワークアウト セッションまたは睡眠セッションの終了時に、ヘルスコネクトにデータを書き込む必要があります。エクササイズや睡眠などのアクティブなトラッキングや、栄養などの手動のユーザー入力の場合、これらのレコードは、アプリがフォアグラウンドにある場合、または 1 日に数回使用されるまれなイベントの場合に作成されます。

イベント期間全体を通じて、アプリでヘルスコネクトの実行が維持されないようにします。

データは、次の 2 つの方法のいずれかを使って、ヘルスコネクトに書き込む必要があります。

  • イベントの完了後にデータをヘルスコネクトに同期します。たとえば、トラッキング対象のエクササイズ セッションをユーザーが終了したときにデータを同期します。
  • WorkManager を使用して、1 回限りのタスクのデータを後で同期するようにスケジュール設定します。

エクササイズ セッションと睡眠セッション

アプリケーションは、少なくとも表 2 の [Expected] 列のガイダンスに従う必要があります。可能な場合は、[ベスト] 列のガイダンスに従ってください。

次の表は、エクササイズ中にデータを書き込む方法を示しています。

表 2: エクササイズ セッション中のデータの書き込みに関するガイダンス

データの種類

単位

想定される

今後ともどうぞよろしくお願いいたします。

例

手順

手順

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 5 歩

23:16 - 23:17 - 22 歩

23:17 - 23:18 - 8 歩

StepsCadence

歩/分

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 35 歩/分

23:16 - 23:17 - 37 歩/分

23:17 - 23:18 - 40 歩/分

車椅子の車輪を押した回数

プッシュ

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 5 回のプッシュ

23:16 - 23:17 - 22 回のプッシュ

23:17 - 23:18 - 8 回のプッシュ

CyclingPedalingCadence

rpm

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 65 rpm

23:16 - 23:17 - 70 rpm

23:17 - 23:18 - 68 rpm

電源

ワット

1 分ごと

1 秒ごと

23:14-23:15 - 250 ワット

23:16 - 23:17 - 255 ワット

23:17 - 23:18 - 245 ワット

速度

km/min

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 0.3 km/分

23:16 - 23:17 - 0.4 km/分

23:17 - 23:18 -0.4 km/分

距離

km/m

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 0.008 km

23:16 - 23:16 - 0.021 km

23:17 - 23:18 - 0.012 km

ActiveCaloriesBurned

カロリー

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 20 カロリー

23:16 - 23:17 - 20 カロリー

23:17 - 23:18 - 25 カロリー

TotalCaloriesBurned

カロリー

1 分ごと

1 秒ごと

23:14 ~ 23:15 - 36 カロリー

23:16 - 23:17 - 36 カロリー

23:17 - 23:18 - 41 カロリー

ElevationGained

m

1 分ごと

1 秒ごと

20:36 - 20:37 - 3.048m

20:39 - 20:40 - 3.048m

23:23 - 23:24 - 9.144m

ExerciseRoutes

lat/lng/alt

3 ~ 5 秒ごと

1 秒ごと

HeartRate

bpm

1 分間に 4 回

1 秒ごと

23:14 ~ 23:15 - 150 bpm

表 3 は、睡眠セッション中または睡眠セッション後にデータを書き込む方法を示しています。

表 3: 睡眠セッション中または睡眠セッション後にデータを書き込む際のガイダンス

データの種類

単位

想定されるサンプル

例

睡眠ステージ

各段階で

睡眠ステージごとの詳細な期間

23:46 ~ 23:50 - 覚醒

23:50 ~ 23:56 - 浅い睡眠

23:56 - 00:16 - 深い睡眠

RestingHeartRate

bpm

1 日の単一の値(朝一番に取得されることが想定されます)

午前 6 時 11 分 - 60 bpm

OxygenSaturation

%

1 日の単一の値(朝一番に取得されることが想定されます)

6:11 - 95.208%

マルチスポーツ イベント

このアプローチでは、既存のデータ型と構造を使用し、現在のヘルスコネクトの実装とデータ リーダーとの互換性を検証します。これは、フィットネス プラットフォームでよく採用されているアプローチです。

また、水泳、自転車、ランニングなどの個々のセッションは、ヘルスコネクト内で本質的にリンクされていません。データ リーダーは、これらのセッション間の関係を時間的な近さに基づいて推測する必要があります。セグメント間のトランジション(水泳からサイクリングへの切り替えなど)は明示的に表されません。

次の例は、トライアスロンのデータを書き込む方法を示しています。

val swimStartTime = Instant.parse("2024-08-22T08:00:00Z")
val swimEndTime = Instant.parse("2024-08-22T08:30:00Z")
val bikeStartTime = Instant.parse("2024-08-22T08:40:00Z")
val bikeEndTime = Instant.parse("2024-08-22T09:40:00Z")
val runStartTime = Instant.parse("2024-08-22T09:50:00Z")
val runEndTime = Instant.parse("2024-08-22T10:20:00Z")

val swimSession = ExerciseSessionRecord(
    startTime = swimStartTime,
    endTime = swimEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ),
    startZoneOffset = null,
    endZoneOffset = null,
)

val bikeSession = ExerciseSessionRecord(
    startTime = bikeStartTime,
    endTime = bikeEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ),
    startZoneOffset = null,
    endZoneOffset = null,
)

val runSession = ExerciseSessionRecord(
    startTime = runStartTime,
    endTime = runEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    metadata = Metadata.activelyRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ),
    startZoneOffset = null,
    endZoneOffset = null,
)

healthConnectClient.insertRecords(listOf(swimSession, bikeSession, runSession))

例外を処理する

ヘルスコネクトは、問題が発生した場合に CRUD 操作について標準的な例外をスローします。すべてのアプリで、これらを適切にキャッチして処理する必要があります。

HealthConnectClient の各メソッドは、スローされる可能性のある例外をリストしますが、一般に次のような処理が必要です。

表 1: ヘルスコネクトの例外と推奨されるベスト プラクティス
例外 説明 推奨されるベスト プラクティス
IllegalStateException 次のいずれかの状況が発生した場合にスローされます。

  • ヘルスコネクト サービスを利用できない。
  • リクエストが有効な構造でない(timeRangeFilter に Instant オブジェクトが使用される定期的なバケットでの集計リクエストなど)。

リクエストを処理する前に、入力に関する潜在的な問題に対処します。リクエストで値を直接使用する代わりに、カスタム関数内で変数に値を代入するか、パラメータとして使用することをおすすめします。そうすることで、エラー処理戦略を適用できます。
IOException ディスクのデータの読み取りと書き込みで問題が発生した場合にスローされます。この問題を回避するには、次の方法をお試しください。

  • ユーザー入力をバックアップします。
  • 一括書き込みオペレーション中に発生した問題に対処できるようにします。たとえば、プロセスで問題を越えたことを確認してから、残りの操作を行うようにします。
  • リクエストの問題に対処するために、再試行とバックオフの戦略を適用します。

RemoteException SDK が接続されている基となるサービスでエラーが発生したか、サービスとの通信中にエラーが発生した場合にスローされます。

たとえば、アプリが特定の uid を持つレコードを削除しようとした場合に、基となるサービスでチェックして初めてレコードが存在しないことが検出されると、例外がスローされます。
この問題を回避するには、次の方法をお試しください。

  • アプリのデータストアとヘルスコネクトの間で定期的に同期を実行します。
  • リクエストの問題に対処するために、再試行とバックオフの戦略を適用します。

SecurityException 現在付与されていない権限を必要とするリクエストの場合にスローされます。この問題を回避するには、公開したアプリのヘルスコネクトのデータ型の使用を宣言していることを確認します。また、ヘルスコネクトの権限は、マニフェスト ファイルとアクティビティ内で宣言する必要があります。