Android
Mobile App Development

Health Connect Guide | Kotlin | Jetpack Compose

Pranathi Pellakuru

30 Apr 2024

Angular Project

If you are working on a fitness app , here is the simple tutorial that helps you read data from Health Connect 

Steps to be implemented

Adding the dependency

Add the latest version of HealthConnect SDK dependency in your module-level build.gradle file as follows:

implementation(“androidx.health.connect:connect-client:<latest version>“)

replace <latest version> with the latest version you can find it here.

Checking the Health Connect availability in the device

I will try my best to keep the further steps as simple as possible with minimum number of files 

Let us start with creating an Object file named HealthConnectUtils.kt shown as below , where we’re gonna include all the variables and methods related to Health Connect

object HealthConnectUtils {

   private var healthConnectClient: HealthConnectClient? = null

   fun checkForHealthConnectInstalled(context: Context):Int {

       val availabilityStatus =

           HealthConnectClient.getSdkStatus(context, "com.google.android.apps.healthdata")

       when (availabilityStatus) {

           HealthConnectClient.SDK_UNAVAILABLE -> {

               // The Health Connect SDK is unavailable on this device at the time.

               // This can be due to the device running a lower than required Android Version.

               // Apps should hide any integration points to Health Connect in this case.

           }

           HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> {

               // The Health Connect SDK APIs are currently unavailable, the provider is either not installed

               // or needs to be updated. You may choose to redirect to package installers to find a suitable APK.

           }

           HealthConnectClient.SDK_AVAILABLE -> {

               // Health Connect SDK is available on this device.

               // You can proceed with querying data from Health Connect using the client.

               healthConnectClient = HealthConnectClient.getOrCreate(context)

           }

       }

       return availabilityStatus

   }

}

function checkForHealthConnectInstalled helps in checking whether Health Connect Client is available in the device or not, if available it creates a Health Connect instance and in order for the above function to work properly , you need to declare Health Connect package name in your Android manifest file

<application> ... </application>

  ...

  <!-- Check if Health Connect is installed -->

<queries>

<package android:name="com.google.android.apps.healthdata" />

</queries>

Now call this function in your compose screen using Launched Effect

@Composable

fun HealthConnectScreen(){

   val context = LocalContext.current

   LaunchedEffect(key1 = true) {

       when (HealthConnectUtils.checkForHealthConnectInstalled(context)) {

           HealthConnectClient.SDK_UNAVAILABLE -> {

               Toast.makeText(

                   context,

                   "Health Connect client is not available for this device",

                   Toast.LENGTH_SHORT

               ).show()

           }

           HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> {

                Toast.makeText(

                   context,

                   "Health Connect needs to be installed",

                   Toast.LENGTH_SHORT

               ).show()

           }

           HealthConnectClient.SDK_AVAILABLE -> {

                //Health Connect is available , ask for permissions

           }

       }

   }

}

Asking for the permissions from Health connect to read the data

Once we got the Health Connect client instance , the next step would be requesting for the permissions from the user

 Lets break down this step into smaller steps

STEP 1 : Include the necessary permissions in AndroidManifest.xml file 

<manifest>

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

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

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

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


<application>

  ...

</application>

</manifest>

You can find the list of permissions here and make sure you request access for required permissions by filing the form ,which is compulsory if you are deploying your app to play store.

STEP 2 :

Before Requesting for permissions , lets check whether permissions are already granted or not . Add the below method to your HealthConnectUtils.kt file to serve that purpose

suspend fun checkPermissions(): Boolean {

       val granted = healthConnectClient?.permissionController?.getGrantedPermissions()

       val PERMISSIONS = setOf(

           HealthPermission.getReadPermission(StepsRecord::class),

           HealthPermission.getReadPermission(SleepSessionRecord::class),

           HealthPermission.getReadPermission(DistanceRecord::class),

           HealthPermission.getReadPermission(ExerciseSessionRecord::class)

       )

       if (granted != null) {

           return granted.containsAll(PERMISSIONS)

       }

       return false

   }

 

STEP 3 :

Now based on the response from above function , we might need to request the permissions from user using rememberLauncherForActivityResult. With the addition of permission launcher in the compose screen, it looks something like this

@Composable

fun HealthConnectScreen() {

   val context = LocalContext.current

   val PERMISSIONS = setOf(

           HealthPermission.getReadPermission(StepsRecord::class),

           HealthPermission.getReadPermission(SleepSessionRecord::class),

           HealthPermission.getReadPermission(DistanceRecord::class),

           HealthPermission.getReadPermission(ExerciseSessionRecord::class)

       )

   val requestPermissions =

       rememberLauncherForActivityResult(PermissionController.createRequestPermissionResultContract()) { granted ->

           if (granted.containsAll(PERMISSIONS)) {

               // Permissions successfully granted , continue with reading the data from health connect

           } else {

               Toast.makeText(context, "Permissions are rejected", Toast.LENGTH_SHORT).show()

           }

       }

   LaunchedEffect(key1 = true) {

       when (HealthConnectUtils.checkForHealthConnectInstalled(context)) {

           HealthConnectClient.SDK_UNAVAILABLE -> {

               Toast.makeText(

                   context,

                   "Health Connect client is not available for this device",

                   Toast.LENGTH_SHORT

               ).show()

           }




           HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> {

              Toast.makeText(

                   context,

                   "Health Connect needs to be installed",

                   Toast.LENGTH_SHORT

               ).show()

           }

           HealthConnectClient.SDK_AVAILABLE -> {

               if (HealthConnectUtils.checkPermissions()) {

                   // Permissions already granted, continue reading the data from health connect

               } else {

                   requestPermissions.launch(HealthConnectUtils.PERMISSIONS)

               }

           }

       }

   }

}

STEP 4:

In order to make your activity to launch the permissions request intent , you must add ACTION_SHOW_PERMISSIONS_RATIONALE intent to your activity in the Android manifest file

For android versions 13 and below , add the below intent filter for the activity in which you will be requesting for the Health Connect permissions

<intent-filter>

  <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />

</intent-filter>

And for android versions 14 and above, create an activity alias inside the application to show the rationale of Health Connect permissions

<activity-alias

  android:name="ViewPermissionUsageActivity"

  android:exported="true"

  android:targetActivity="<activity name>"

  android:permission="android.permission.START_VIEW_PERMISSION_USAGE">

  <intent-filter>

      <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />

      <category android:name="android.intent.category.HEALTH_PERMISSIONS" />

  </intent-filter>

</activity-alias>

replace <activity name> with your activity in which you will be asking for permissions

With the above step, you should be able to see the Health Connect permission screen showing up as soon as the composable is rendered 

          permissions screen

Reading the fitness data from Health Connect

Once you acquire permissions for Health Connect, you can proceed to perform actions such as reading from Health Connect and writing to Health Connect. If an app has written records to Health Connect before, it is also possible for that app to update or delete those records. However, in this article, we will focus only on reading 

You can read the data in two ways, raw and aggregate

Raw :

Include the below function in HealthConnectUtils.kt to read the data in their original format i.e, as records

suspend fun readStepsByTimeRange(

  startTime: Instant,

  endTime: Instant

) {

  try {

      val response = healthConnectClient?.readRecords(

          ReadRecordsRequest(

              StepsRecord::class,

              timeRangeFilter = TimeRangeFilter.between(startTime, endTime)

          )

      )

      for (stepRecord in response.records) {

          // Process each step record

      }

  } catch (e: Exception) {

      // Run error handling here

  }

}

Aggregate :

Reading this way provides you the merged data in case of duplicate records from various apps within the same time frame. The below code shows basic cumulative aggregation, but there are other kinds of aggregations as well. I recommend reading more about them from here

suspend fun aggregateSteps(

  startTime: Instant,

  endTime: Instant

) {

  try {

      val response = healthConnectClient?.aggregate(

          AggregateRequest(

              metrics = setOf(StepsRecord.COUNT_TOTAL),

              timeRangeFilter = TimeRangeFilter.between(startTime, endTime)

          )

      )

      // The result may be null if no data is available in the time range

      val totalSteps = dailyResult.result[StepsRecord.COUNT_TOTAL]

  } catch (e: Exception) {

      // Run error handling here

  }

}

We can call these functions once permissions are given in your compose screen as shown below

@Composable

fun HealthConnectScreen() {

   val context = LocalContext.current

   val scope = rememberCoroutineScope()

   val PERMISSIONS = setOf(

           HealthPermission.getReadPermission(StepsRecord::class),

           HealthPermission.getReadPermission(SleepSessionRecord::class),

           HealthPermission.getReadPermission(DistanceRecord::class),

           HealthPermission.getReadPermission(ExerciseSessionRecord::class)

       )

   val requestPermissions =

       rememberLauncherForActivityResult(PermissionController.createRequestPermissionResultContract()) { granted ->

           if (granted.containsAll(PERMISSIONS)) {

               scope.launch {

                   HealthConnectUtils.readStepsByTimeRange(startTime,endTime)

               }

           } else {

               Toast.makeText(context, "Permissions are rejected", Toast.LENGTH_SHORT).show()

           }

       }

   LaunchedEffect(key1 = true) {

       when (HealthConnectUtils.checkForHealthConnectInstalled(context)) {

           HealthConnectClient.SDK_UNAVAILABLE -> {

               Toast.makeText(

                   context,

                   "Health Connect client is not available for this device",

                   Toast.LENGTH_SHORT

               ).show()

           }

           HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> {

              Toast.makeText(

                   context,

                   "Health Connect needs to be installed",

                   Toast.LENGTH_SHORT

               ).show()

           }

           HealthConnectClient.SDK_AVAILABLE -> {

               if (HealthConnectUtils.checkPermissions()) {

                   HealthConnectUtils.readStepsByTimeRange(startTime,endTime)

               } else {

                   requestPermissions.launch(HealthConnectUtils.PERMISSIONS)

               }

           }

       }

   }

}

That’s it, by tweaking these functions to return data, you’ll be able to showcase data from Health Connect in your UI. Consider it as your next move forward.