見出し画像

AndroidアプリでヘルスコネクトAPIを活用して歩数データを取得する方法

こんにちわ、株式会社フィノバレーでモバイルアプリチームリーダーを担当している照井です。
以前からGoogleFit APIは非推奨となりヘルスコネクトへの移行を促す話は挙がっていましたが、今年の5月にGoogleFit APIサービスの終了が正式に発表されました。
https://developer.android.com/health-and-fitness/guides/health-connect/migrate/fit-apis-end-of-service?hl=ja

これまでGoogleFit APIで取得していた身体活動情報は、ヘルスコネクトというアプリおよびAPIを使用して取得することになります。
弊社が提供するMoneyEasyでも、GoogleFit APIを使用して歩数情報を取得し、健康推進をするヘルスケア機能をいくつかの環境で提供しています。

ヘルスコネクトアプリとAPIがどんなものでどのように歩数情報を取得するのか等を検証アプリを作成しながら調査しました。本記事ではその調査内容について記載します。
調査および検証アプリの作成を行うにあたり、AndroidのDevelopersサイトのヘルスコネクトページとCodeLabを参考にしました。
(※本記事の文字のリンクは全てAdnroidのDevelopersサイトへのリンクとなっております。)

[注意点]
この記事は2024年7月12日現在の情報で記載しており、検証アプリで使用したヘルスコネクトのライブラリはconnect-client:1.1.0-alpha02となります。
Developersサイトを一通り流し見した感じでは、ヘルスコネクトアプリとそのAPIは今後そこそこ更新が入ることが予想されます。破壊的変更が入ることはないと信じたいのですが、ライブラリのバージョンアップと共に実装内容や推奨フローが多少変わる可能性は考慮しておいた方が良さそうです。
また、本記事作成時点で、オンボーディング機能はAndroid14未対応なので検証アプリには入れていません。(最後に少しだけ記載しました)


ヘルスコネクトとGoogleFitの違い

まず開発視点で両者の違いはなんだろう?と調べた結果、よくある質問のヘルスコネクトと Google Fit の違いは何ですか?というページがとてもわかりやすく比較表にしてくれていました。

ヘルスコネクトはデバイスベースとなっているため、開発するアプリで歩数情報を取得する場合は端末にインストールしたヘルスコネクトアプリから取得することになります。
これまでのGoogleFit APIはGoogleアカウントベースだったため「OAuth ベースの権限」となり、歩数情報を取得する際にGoogleアカウントとの紐付け処理が必要でした。また、アプリの審査とは別にOAuth Verifaication審査を行う必要もありました。
この審査は非常に大変で最長1ヶ月ほどかかる上、毎年審査が必要という仕組みになっており高負担となっていました。しかし、ヘルスコネクトを使用すればOAuthの審査は不要になるようです。素晴らしいです。

検証アプリの画面フロー

作成した検証アプリとヘルスコネクトの関連フローは以下の図の通りです。
(※ 検証アプリのため、このフロー図はDeveloperサイトのガイドラインに厳密に沿っていません。ご了承ください。)

作成した検証アプリの画面フロー

権限周りの処理
ヘルスコネクトAPIは以下のようなチェック関数が用意されています。

  • 端末にヘルスコネクトアプリがインストールされているか

  • 必要な権限が許可されているか

権限周りの処理はDeveploersサイトのヘルスコネクトを使ってみるやCodeLabの3.権限の管理を参考にしました。

ヘルスコネクトアプリのインストールチェック
検証アプリではインストールされているかどうかの判定結果をLiveDataに流すようにしましたので実装例は以下のようになります。

fun checkInstalled(context: Context) {
  val isAvailable = HealthConnectClient.getSdkStatus(context) == SDK_AVAILABLE
  mutableIsInstalled.postValue(isAvailable)
}

インストール済みの場合は次の状態へ進み、未インストールの場合はGooglePlayストアのヘルスコネクトアプリページに遷移するボタンやリンクを用意してあげるのが良いかなと思います。

注意点としてgetSdkStatus関数はSDKの判定になるのでAndroid9未満の端末でfalseが返ってきた場合、ヘルスコネクトアプリのストアページに誘導してもヘルスコネクトアプリはインストールできません。
(※ ヘルスコネクトSDKとアプリのサポートについてはプラットフォーム アーキテクチャを確認するの注を参照ください。)

CodeLabのコード(HealthConnectManagerクラスのcheckAvailability関数)では、getSdkStatusのチェックの次にOSのサポートチェックもしています。
プロダクトに組み込む際はこのチェックも入れたほうがユーザーに親切かなと思いました。

歩数権限の許諾
次に権限チェックとヘルスコネクトアプリでの権限許諾周りの実装です。まず、ヘルスコネクトと連携する役割のクラスHealthConnectを別途作り、HealthConnectクラスに必要な実装を入れました。
かなり省略していますが、必要な箇所だけ抜き出すと以下のようになります。

class HealthConnect constructor() {

  // 省略

  // permissionはSet型で作ります。今回は歩数情報だけなので1つです
  val permissions = setOf(
    HealthPermission.getReadPermission(StepsRecord::class)
  )

  fun checkHasPermission() {
    // getGrantedPermissions()がsuspend関数なのでlaunch内で実行します
    launch {
      val hasPermission = healthConnectClient.permissionController.getGrantedPermissions().containsAll(permissions)
      // 後続処理
    }
  }

  /**
   * ヘルスコネクトアプリと連携するためのregisterForActivityResultの引数に指定するContractです
   */
  fun requestPermissionsActivityContract(): ActivityResultContract<Set<String>, Set<String>> {
    return PermissionController.createRequestPermissionResultContract()
  }
}

次はヘルスコネクトと連携する画面の実装です。検証アプリではMainActivityでこの実装を入れました。ここも必要な実装のみ抜き出しています。

// healthConnectは自前のHealthConnectクラスのインスタンスです。
//  初回はヘルスコネクトアプリが起動し、許諾画面が表示されます。
private val permissionsLauncher = registerForActivityResult(healthConnect.requestPermissionsActivityContract()) {
  healthConnect.checkHasPermission()
}

override fun onCreate(savedInstanceState: Bundle?) {

  // 省略

  binding.connectButton.setOnClickListener {
    permissionsLauncher.launch(healthConnect.permissions)
  }
}

permissionsLauncher.launchが実行されると、初回はヘルスコネクトアプリが起動してhealthConnect.permissionsで指定した権限の許可設定が行えます。
RuntimePermissionの特性上、ヘルスコネクトアプリの許可画面で「キャンセル」か「許可」のいずれかを選択すると2回目以降はpermissionsLauncher.launchを呼んでもヘルスコネクトアプリは起動しません。なお、Developersサイトによるとヘルスコネクトアプリの起動リンクはユーザーにわかりやすい位置に用意する必要がありそうなので続けて起動リンクについて書いていきます。

ヘルスコネクトアプリの起動リンク
権限とデータに関する UI のガイドラインに色々注意事項が記載されていますが、その中にヘルスコネクトとのリンクについて記載されてる部分があります。
内容を読むと、アプリ利用ユーザーが簡単にヘルスコネクトアプリを起動し、権限の許可や拒否ができるようにする必要がありそうです。CodeLabのアプリでもヘルスコネクトアプリは簡単に起動できるようになっていました。
ヘルスコネクトアプリの起動はアクションで提供されており、以下の実装で起動できました。

private fun startHealthConnectApp() {
  val intent = Intent().apply {
    action = "androidx.health.ACTION_HEALTH_CONNECT_SETTINGS"
  }
  startActivity(intent)
}

権限周りの処理は以上になります。

プライバシーポリシーについて

ヘルスコネクトアプリでは、いつでもアプリのプライバシーポリシーを参照できるようリンクが設けられています。そのため、アプリのプライバシーポリシー画面をActionとして登録しておく必要があります。

検証アプリでのAndroidManifest.xmlの定義を載せます。アプリのプライバシー ポリシーのダイアログを表示するに記載されている内容を参考にしています。(この定義で動きますが、正しいかは若干怪しいです)
プライバシーポリシー画面(PrivacyPolicyActivity)を作って、この定義でアクションを設定しました。

<!-- ヘルスコネクトのPrivacyPolicyリンクをタップするとアクションが実行されてこの画面が起動する -->
<activity
    android:name=".PrivacyPolicyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
        <category android:name="android.intent.category.HEALTH_PERMISSIONS"/>
    </intent-filter>
    <intent-filter>
        <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

集計関数で日単位の歩数を取得する

基本的な集計関数によると、1日ごとの合計歩数は集計関数(AggregateXX)を使えば取得できそうです。以下、今回の検証コードです。

fun onLoadStepCounts() {
  launch {
      val now = LocalDateTime.now()
      val endTime = LocalDateTime.of(now.year, now.month, now.dayOfMonth, 23, 59)
      val startAt = endTime.minusDays(30)
      val startTime = LocalDateTime.of(startAt.year, startAt.month, startAt.dayOfMonth, 0, 0)

      val request = AggregateGroupByPeriodRequest(
          metrics = setOf(StepsRecord.COUNT_TOTAL),
          timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
          timeRangeSlicer = Period.ofDays(1)
      )

      val response = healthConnectClient.aggregateGroupByPeriod(request)
      
      // responseは集計データの配列になっており、result[StepsRecord.COUNT_TOTAL]で歩数の集計値が取得できます。
      // 後続処理
  }
}

AggregateGroupByPeriodRequestのtimeRangeSlicerに指定しているPeriod.ofDays(1)ですが、TimeRangeFilter.betweenのstartTimeに指定した時刻から24時間単位でendTimeまで取得するという動きになっていました。
例えば処理が走る時刻が17:13とすると「17:13から翌日の17:13まで」の集計を返してきます。
そのため、検証アプリではstartTimeは0時0分、endTimeは23時59分固定としました。

なお、取得したい情報の権限はAndroidManifest.xmlで定義しておく必要があります。一覧は権限ページの1.0.0-alpha10 以降の場合に記載されている通りで、検証アプリでは歩数情報のみ欲しかったのでAndroidManifest.xmlに以下の通り定義しました。

<uses-permission android:name="android.permission.health.READ_STEPS"/>

他にGoogleFit APIとは異なる点として、取得可能な過去データは最大30日前までとなります。詳細は30日間の読み取り制限も参照ください。

注意点: レート制限について
ヘルスコネクトAPIはよくあるAPIと同様、レート制限が設けられているため短時間で連続実行されるような実装は避けた方が良さそうです。私は検証アプリでヘルスコネクトAPIを短時間で連続実行したら以下のようなエラーが発生しました。
「Request rejected. Rate limited request quota has been exceeded. Please wait until quota has replenished before making further requests.」

リアルタイム性を重視する場合はデータを同期するに書かれているヘルスコネクトIDを使った同期処理を検討した方が良さそうです。
今回の検証では同期処理を試していないのですが、今後の検討でこの機能を使う可能性もあると考えています。

オンボーディング機能について

冒頭の注意事項にも記載しましたが、ヘルスコネクトAPIはおそらくまだ過渡期でDevelopersサイトのページも更新がそれなりに行われていると思います。
この記事を書いている時点ではオンボーディング機能は未提供でしたが、こちらのオンボーディング計画ページによると、近日中にACTION_SHOW_ONBOARDINGというアクションを実装すると書かれています。 この機能を使ってユーザーにヘルスコネクトのガイドをしていくことが推奨されると思われます。

まとめ

今回はヘルスコネクトAPIを使って歩数情報をどう取得するのかを目的に調査および検証しました。
最初ちょっと身構えていましたが、GoogleFit APIに比べて権限や歩数取得などはかなり簡単に実装することができると感じました。
この調査結果を元に、MoneyEasyのヘルスケア機能にどう組み込んでいくのか検討し、GoogleFit APIから移行していく予定です。

この記事が気に入ったらサポートをしてみませんか?