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.