4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Android-ヘルスコネクトを使って単位時間ごとのデータを取得する

Posted at

この記事はどんな内容?

この記事でやることの詳細

  • ヘルスコネクトを介して、2種類の方法でGoogleFit経由で取得している歩数データを取得します
    • 基本的なデータのとり方
    • 単位時間(今回は1日)ごとのデータのとり方

基本的なデータのとり方

HealthConnectClient.readRecords()を使用して、取得したいデータ型と期間だけを指定してデータを取得します。

        val startTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0)
        val endTime  = LocalDateTime.of(2024, 1, 6, 0, 0, 0)

        val response  = client.readRecords(
            ReadRecordsRequest(
                StepsRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
        var step = 0L
        for (data in response.records){
            step += data.count
        }

この形式だと返ってくるデータ(ReadRecordsResponse)の取得単位がまちまちです(1分単位だったり10分以上間隔があいたり)

単位時間ごとのデータのとり方

HealthConnectClient.aggregateGroupByDuration()を使用して、取得したいデータ型と期間、データのを指定してデータを取得します。
特定アプリで取得したデータだけを返す設定も追加しています

        val startTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0)
        val endTime  = LocalDateTime.of(2024, 1, 6, 0, 0, 0)

        val response  = client.aggregateGroupByDuration(
            AggregateGroupByDurationRequest(
                metrics = setOf(StepsRecord.COUNT_TOTAL),
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                timeRangeSlicer = Duration.ofDays(1),
                // Note: Google Fitで取得したデータだけとるようにする
                dataOriginFilter = setOf(DataOrigin("com.google.android.apps.fitness"))
            )
        )
        for (data in response){
            step += data.result[StepsRecord.COUNT_TOTAL] ?: 0L
        }
        

1日ごとの5つのデータ、5日分のデータが返ってきます

今回のサンプルコード

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val  HEALTH_CONNECT_PERMISSIONS = setOf(
        HealthPermission.getReadPermission(StepsRecord::class)
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onStart() {
        super.onStart()
        val healthConnectClient: HealthConnectClient = getHealthConnectClient() ?: return

        displayHealthConnectSDKStatus(onOKClick = {
            lifecycleScope.launch {
                requestPermission(healthConnectClient)
            }
        })
    }

    private fun displayHealthConnectSDKStatus(onOKClick: () -> Unit){
        val status = getHealthConnectSDKStatus()
        var text = "";

        when (status){
            SDK_AVAILABLE  -> {
                text = "API利用可能です"
            }
            SDK_UNAVAILABLE ->{
                text = "SDKを利用できません"
            }
            SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED ->{
                text = "プロバイダーがインストールされていません"
            }
        }

        AlertDialog.Builder(this)
            .setTitle("API利用可否")
            .setTitle(text)
            .setPositiveButton("OK", DialogInterface.OnClickListener { _, _ ->
                onOKClick()
            })
            .show()
    }

    private fun getHealthConnectSDKStatus(): Int{
        // Note: ProviderNameは無くてもいい
        // AndroidManifestに入れたヘルスコネクトパッケージ名を入れている
        val providerName = "com.google.android.apps.healthdata"
        return HealthConnectClient.getSdkStatus(this, providerName)
    }

    private fun getHealthConnectClient():  HealthConnectClient?{
        if  (getHealthConnectSDKStatus() == SDK_AVAILABLE){
            return HealthConnectClient.getOrCreate(this)
        }
        return null
    }

    private fun onPermissionAcceptedAction(){
        lifecycleScope.launch{
            val healthConnectClient: HealthConnectClient = getHealthConnectClient() ?: return@launch
            val step = readStepRecord(healthConnectClient)
            Toast.makeText(this@MainActivity,  "歩数は"+step+"歩", Toast.LENGTH_LONG).show()
        }
    }

    //  NOTE: registerForActivityResultを使う場合はライフサイクルがSTARTED前に定義しないといけないため、プロパティとして定義する
    private val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()
    private val requestPermissions = registerForActivityResult(requestPermissionActivityContract) {granted ->
        if(granted.containsAll(HEALTH_CONNECT_PERMISSIONS)){
            Toast.makeText(this,  "全ての権限があります", Toast.LENGTH_LONG).show()
            onPermissionAcceptedAction()
        }else{
            Toast.makeText(this,  "権限が許可されませんでした", Toast.LENGTH_LONG).show()
        }
    }

    private suspend fun requestPermission(client: HealthConnectClient){
        val granted = client.permissionController.getGrantedPermissions()

        if (granted.containsAll(HEALTH_CONNECT_PERMISSIONS)){
            Log.d("MyLog", "権限は全て付与")
            onPermissionAcceptedAction()
        }else{
            Log.d("MyLog", "権限はありません")
            requestPermissions.launch(HEALTH_CONNECT_PERMISSIONS)
        }
    }


    private suspend fun readStep(client: HealthConnectClient, startTime: LocalDateTime, endTime: LocalDateTime): Long{
        var step = 0L;
        try {
            val response  = client.readRecords(
                ReadRecordsRequest(StepsRecord::class,
                    timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                    dataOriginFilter = emptySet()))
            for (data in response.records){
                step += data.count
            }
        }catch (e: Exception){
            Log.d("MyLog", "歩数取得に失敗")
        }
        return step
    }

    // NOTE:  https://developer.android.com/guide/health-and-fitness/health-connect/common-workflows/aggregate-data?hl=ja
    private suspend  fun readDurationStep(client: HealthConnectClient, startTime: LocalDateTime, endTime: LocalDateTime): Long{
        var step = 0L;
        try {
            val response  = client.aggregateGroupByDuration(
                AggregateGroupByDurationRequest(
                    metrics = setOf(StepsRecord.COUNT_TOTAL),
                    timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                    timeRangeSlicer = Duration.ofDays(1),
                    // Note: Google Fitで取得したデータだけとるようにする
                    dataOriginFilter = setOf(DataOrigin("com.google.android.apps.fitness"))
                )
            )
            for (data in response){
                step += data.result[StepsRecord.COUNT_TOTAL] ?: 0L
            }

        }catch (e: Exception){
            Log.d("MyLog", "歩数取得に失敗")
        }
        return step
    }

    private suspend fun readStepRecord(client: HealthConnectClient): Long{
        val startTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0)
        val endTime  = LocalDateTime.of(2024, 1, 6, 0, 0, 0)

        // return readStep(client, startTime, endTime)
        return readDurationStep(client, startTime, endTime)
    }
}
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?