Your First Health Connect Integrated App

Stay organized with collections Save and categorize content based on your preferences.

1. Introduction

e4a4985ad1cdae8b.png

Last Updated: 2022-09-21

What is Health Connect?

Health Connect is a health data platform for Android app developers. It provides a single, consolidated interface for access to users' health & wellness data, and consistent functional behavior across all devices. With Health Connect, users have a secure health & wellness data storage on-device, with full control and transparency over access.

How does Health Connect work?

Health Connect supports more than 50 common health & fitness data types and categories, including: activity, sleep, nutrition, body measurements and vitals like heart rate and blood pressure.

How Health Connect Works

With user permission, developers can securely read from and write data to Health Connect, using standardized schemas and API behavior. Users will have full control over their privacy settings, with granular controls to see which apps are requesting access to data at any given time. The data in Health Connect is stored on-device and encrypted. Users will have the ability to shut off access or delete data they don't want on their device, and the option to prioritize one data source over another when using multiple apps.

Health Connect Architecture

architecture

The following is an explanation of Health Connect's key aspects and architectural components:

  • Client app - to integrate with Health Connect, the client app first links then SDK into their health and fitness app. This provides an API surface to interact with the Health Connect API.
  • Software Development Kit - the SDK enables the client app to communicate with the Health Connect APK over IPC.
  • Health Connect APK - this is the APK that implements Health Connect. It contains both its Permissions Management and Data Management components. The Health Connect APK is made available directly on the user's device, making Health Connect device-centric instead of account-centric.
  • Permissions management - Health Connect includes a user interface through which apps request the user's permission to display data. It also provides a list of existing user permissions. This allows users to easily control and manage the access they have granted or denied to various applications.
  • Data management - Health Connect provides a user interface with an overview of recorded data, whether it's a user's step counts, cycling speeds, heart rate, or other supported data types.

What you'll build

In this codelab, you're going to build a simple Health and Fitness app integrated with Health Connect. Your app will:

  • Obtain and check user permissions for data access
  • Write data to Health Connect
  • Read aggregated data from Health Connect

What you'll learn

  • How to setup your environment to support Health Connect integration development
  • How to obtain permissions and execute permission checks
  • How to contribute health & fitness data to the Health Connect platform
  • How to benefit from the on-device data storage
  • How to validate your app with Google provided developer tools

What you'll need

  • Latest stable version of Android Studio
  • Android mobile device with Android SDK version 28 (Pie) or higher

2. Getting set up

Install Health Connect

Health Connect is available on Play Store, install Health Connect to your mobile device to handle all requests sent by your application using the Health Connect SDK. Scan the QR code below to install Health Connect.

633ed0490a74595d.png

Get the sample code

The sample directory contains the start and finished code for this codelab. In the Project view on the left of Android Studio you will find two modules:

  • start – the starter code for this project; you will make changes to this to complete the codelab
  • finished – the completed code for this codelab; used to check your work

Explore the start code

The Codelab sample app has basic UIs built by Jetpack Compose with the following screens:

  • WelcomeScreen is the app's landing page and shows different messages depending on the Health Connect availability (installed, not installed, not supported).
  • PrivacyPolicyScreen explains the app's use of permissions and is shown to the user when the user clicks on the "privacy policy" link in the Health Connect permissions dialog.
  • InputReadingsScreen demonstrates reading and writing simple weight records.
  • ExerciseSessionScreen allows inserting and listing out exercise sessions. When clicking on the record, it takes the user to the ExerciseSessionDetailScreen to show more data associated with the session.
  • DifferentialChangesScreen demonstrates how to get a Changes token and get new changes from Health Connect.

HealthConnectManager stores all the functions that interact with Health Connect. In this codelab we will guide you step by step to complete the essential functionalities. The <!-- TODO: strings within the start code have corresponding sections in this codelab where sample codes are provided for you to insert into the project.

Let's start by adding Health Connect to the project!

Add Health Connect client SDK

To start using the Health Connect SDK, you need to add a dependency to the build.gradle file. To find the latest version of Health Connect, check the Jetpack library release.

dependencies {
    // Add a dependency of Health Connect SDK
    implementation "androidx.health.connect:connect-client:1.0.0-alpha06"
}

Declare Health Connect visibility

To interact with Health Connect within the app, declare the Health Connect package name in the AndroidManifest.xml:

<!-- TODO: declare Health Connect visibility -->
<queries>
   <package android:name="com.google.android.apps.healthdata" />
</queries>

Run the start project

At this point, you should be able to see the welcome screen showing "Health Connect is installed on this device" and a menu drawer. We will add the functionalities to interact with Health Connect in later sections.

d54773774e4dc9f.png 462cd7b6cf553ad.png

3. Permission control

Health Connect recommends developers to restrict permission requests to the data types that are used in the app. Blanket permission requests reduce user confidence in the app and could lower user trust. If a permission is denied more than twice, your app will be locked out (i.e. permission requests will no longer appear).

For the purpose of this codelab, we will only need the following permissions:

  • Exercise Session
  • Heart Rate
  • Steps
  • Total Calories Burned
  • Weight

Declare permissions

Declare the permissions your app will use. Create an array resource in res/values/health_permissions.xml. You can view the full mapping of data types to permissions here. Note that you will need to add a line for every permission your app will use:

<!-- TODO: declare Health Connect permissions -->
<resources>
  <array name="health_permissions">
    <item>androidx.health.permission.ExerciseSession.READ</item>
    <item>androidx.health.permission.ExerciseSession.WRITE</item>
    <item>androidx.health.permission.Steps.READ</item>
    <item>androidx.health.permission.Steps.WRITE</item>
    <item>androidx.health.permission.HeartRate.READ</item>
    <item>androidx.health.permission.HeartRate.WRITE</item>
    <item>androidx.health.permission.Weight.READ</item>
    <item>androidx.health.permission.Weight.WRITE</item>
    <item>androidx.health.permission.TotalCaloriesBurned.READ</item>
    <item>androidx.health.permission.TotalCaloriesBurned.WRITE</item>
  </array>
</resources>

Declare the resource in AndroidManifest.xml:

<!-- TODO: Required to specify which Health Connect permissions the app can request -->
<meta-data
    android:name="health_permissions"
    android:resource="@array/health_permissions"/>

Declare the below intent filter in AndroidManifest.xml to handle intent that will explain your app's use of permissions. Your app needs to handle this intent and display a privacy policy describing how the user's data will be used and handled. This intent is sent to the app when the user clicks on the "privacy policy" link in the Health Connect permissions dialog.

<!-- TODO: Add intent filter to handle permission rationale intent -->
<intent-filter>
    <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>

Now re-run the app to see the declared permissions. Click "Settings" from the menu drawer, it takes you to the Health Connect Settings screen. Click on "App permissions", you should see "Health Connect Codelab" in the list. Click on Health Connect Codelab and you will see a list of data types for read and write access.

fbed69d871f92178.png 1b9c7764c1dbdfac.png

Request permissions

Beside directly taking users to Health Connect Settings to manage permissions. You can also request permissions from your app via Health Connect APIs. In the codelab project, we send permission requests before reading or writing data.

HealthConnectClient is an entry point to the Health Connect API. In HealthConnectManager.kt, obtain a HealthConnectClient instance.

private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) }

To start the request permissions dialogue within your application, first build a set of permissions for required data types. You should only request permissions to the data types that you are about to use. For example, in the Record weight screen, you only need to grant read and write permissions for Weight. We have created a permission set in InputReadingsViewModel.kt as shown below.

  val permissions = setOf(
    HealthPermission.createReadPermission(WeightRecord::class),
    HealthPermission.createWritePermission(WeightRecord::class),
  )

Then, check if permissions have already been granted before launching the permission request. In HealthConnectManager.kt, use getGrantedPermissions to check if the permission of the required data types are granted. To launch the permission request, first you have to create an ActivityResultContract by using PermissionController.createRequestPermissionResultContract()from Health Connect SDK, and then launch it if the required permissions are not granted.

  suspend fun hasAllPermissions(permissions: Set<HealthPermission>): Boolean {
    return permissions == healthConnectClient.permissionController.getGrantedPermissions(
      permissions
    )
  }

  fun requestPermissionsActivityContract(): ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>> {
    return PermissionController.createRequestPermissionResultContract()
  }

In the codelab sample app, you may see the Request permissions button shown on the screen if you have not granted the permissions to the required data types. Click on the Request permissions button, it will take you to the Health Connect permission dialog. Allow the required permissions and get back to the codelab app.

626eedcec23659ce.png 6df6cf0e5c4a1a9e.png

4. Write data

Let's start writing records into Health Connect. To write a Weight record, create a WeightRecord object with the weight input value. Note that Health Connect SDK supports various units classes, for example, use Mass.kilograms(weightInput) to set users weight in kilograms.

All data written to Health Connect should specify Zone Offset information. Specifying Zone Offset information while writing data provides time zone information when reading data in Health Connect.

After creating the weight record, use healthConnectClient.insertRecords to write the data into Health Connect.

/**
* TODO: Writes [WeightRecord] to Health Connect.
*/
suspend fun writeWeightInput(weightInput: Double) {
   val time = ZonedDateTime.now().withNano(0)
   val weightRecord = WeightRecord(
       weight = Mass.kilograms(weightInput),
       time = time.toInstant(),
       zoneOffset = time.offset
   )
   val records = listOf(weightRecord)
   try {
      healthConnectClient.insertRecords(records)
      Toast.makeText(context, "Successfully insert records", Toast.LENGTH_SHORT).show()
   } catch (e: Exception) {
      Toast.makeText(context, e.message.toString(), Toast.LENGTH_SHORT).show()
   }
}

Now let's run the app. Click on Record weight and enter a new weight record in kilograms. To verify if the weight record is successfully written to Health Connect, open Health Connect Settings and click on Data and access -> Body measurements -> Weight -> See all entries. You should see the new weight record written from Health Connect Codelab.

Writing exercise sessions

A session is a time interval during which a user performs an activity. An exercise session in Health Connect can include anything from running to badminton. Sessions allow users to measure time-based performance. This data records an array of instantaneous samples measured over a period of time, for example a continuous heart rate or location samples during an activity.

The following example shows you how to write an exercise session. Use healthConnectClient.insertRecords to insert multiple data records associated with a session. The insertion request in this example includes ExerciseSessionRecord with ExerciseType, StepsRecord with steps count, TotalCaloriesBurnedRecord with Energy and series of HeartRateRecord samples.

  /**
   * TODO: Writes an [ExerciseSessionRecord] to Health Connect.
   */
  suspend fun writeExerciseSession(start: ZonedDateTime, end: ZonedDateTime) {
    healthConnectClient.insertRecords(
      listOf(
        ExerciseSessionRecord(
          startTime = start.toInstant(),
          startZoneOffset = start.offset,
          endTime = end.toInstant(),
          endZoneOffset = end.offset,
          exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
          title = "My Run #${Random.nextInt(0, 60)}"
        ),
        StepsRecord(
          startTime = start.toInstant(),
          startZoneOffset = start.offset,
          endTime = end.toInstant(),
          endZoneOffset = end.offset,
          count = (1000 + 1000 * Random.nextInt(3)).toLong()
        ),
        TotalCaloriesBurnedRecord(
          startTime = start.toInstant(),
          startZoneOffset = start.offset,
          endTime = end.toInstant(),
          endZoneOffset = end.offset,
          energy = Energy.calories((140 + Random.nextInt(20)) * 0.01)
        )
      ) + buildHeartRateSeries(start, end)
    )
  }

  /**
   * TODO: Build [HeartRateRecord].
   */
  private fun buildHeartRateSeries(
    sessionStartTime: ZonedDateTime,
    sessionEndTime: ZonedDateTime,
  ): HeartRateRecord {
    val samples = mutableListOf<HeartRateRecord.Sample>()
    var time = sessionStartTime
    while (time.isBefore(sessionEndTime)) {
      samples.add(
        HeartRateRecord.Sample(
          time = time.toInstant(),
          beatsPerMinute = (80 + Random.nextInt(80)).toLong()
        )
      )
      time = time.plusSeconds(30)
    }
    return HeartRateRecord(
      startTime = sessionStartTime.toInstant(),
      startZoneOffset = sessionStartTime.offset,
      endTime = sessionEndTime.toInstant(),
      endZoneOffset = sessionEndTime.offset,
      samples = samples
    )
  }

5. Health Connect Toolbox

Introduction to the Health Connect Toolbox

The Health Connect Toolbox is a companion developer tool to help you easily test your app's integration with Health Connect. The Health Connect Toolbox can easily read and write data directly to Health Connect, allowing you to easily test your app's CRUD operations.

In this codelab, we are going to use the Health Connect Toolbox to test the reading and writing functionalities you just implemented.

Setup Health Connect Toolbox

Use adb to install the Toolbox APK on a connected device with the command:

$ adb install HealthConnectToolbox-{Version Number}.apk

The first time you open the Health Connect Toolbox app, you will be taken to the permission settings under Apps > Special app access > Display over other apps. This permission allows Health Connect Toolbox to show an overlay on top of other apps so that you can easily test reading and writing data without leaving the app you are developing.

Then, from the main screen of the Toolbox you can open the Health Connect app or directly go to the permission flow to allow all Health Connect read and write permissions for testing later.

eaa2f10b251b0328.png

Reading and Writing Health Records

Health Connect Toolbox supports reading and writing all Health Connect data types. In the last session of the codelab, you successfully wrote Weight and Exercise Session records to Health Connect. Let's check if you can read the data from the Health Connect Toolbox.

Before reading and writing to Health Connect, permission from the user must be obtained. The same is true for the Health Connect Toolbox. First, accept the permission request from Health Connect Toolbox. Next, click on the search icon 1f407c55884bb8c3.png from the overlay menu to open the dialog, select the data type (ex: Weight) and click the READ HEALTH RECORD button. You should see the records from the codelab sample app that you just wrote to Health Connect.

To insert a record into Health Connect, click on the edit icon 10c524823c596aea.png from the overlay menu to open the dialog and then select the data type. Let's insert a Weight record from the Toolbox. In the next session, we will show you how to read the record via Health Connect API and display the data in your app.

b0554d1b99bd4237.png

6. Read data

Now you have written Weight and Exercise Session records with both the Codelab sample app or the Toolbox app. Let's use the Health Connect API to read those records. First create a ReadRecordsRequest and specify the record type and the time range to read from. The ReadRecordsRequest can also set a dataOriginFilter to specify the source app of the record you want to read from.

    /**
     * TODO: Reads in existing [WeightRecord]s.
     */
    suspend fun readWeightInputs(start: Instant, end: Instant): List<WeightRecord> {
        val request = ReadRecordsRequest(
            recordType = WeightRecord::class,
            timeRangeFilter = TimeRangeFilter.between(start, end)
        )
        val response = healthConnectClient.readRecords(request)
        return response.records
    }
  /**
   * TODO: Obtains a list of [ExerciseSessionRecord]s in a specified time frame.
   */
  suspend fun readExerciseSessions(start: Instant, end: Instant): List<ExerciseSessionRecord> {
    val request = ReadRecordsRequest(
      recordType = ExerciseSessionRecord::class,
      timeRangeFilter = TimeRangeFilter.between(start, end)
    )
    val response = healthConnectClient.readRecords(request)
    return response.records
  }

Now let's run the app and check if you can see a list of weight records and exercise sessions.

a08af54eef6bc832.png 3b0781389f1094a1.png

7. Reading differential data

The Health Connect Differential Changes API helps to track changes from a specific point of time for a set of data types. For example, if you want to know if users have updated or deleted any existing records outside your app so that you can update your database accordingly.

Reading data with Health Connect is restricted to applications running in the foreground. This restriction is in place to further strengthen user privacy. It notifies and assures users that Health Connect does not have background read access to their data and that data is only read and accessed in the foreground. When the app is in the foreground, the Differential Changes API enables developers to retrieve changes made to Health Connect by deploying a changes token.

In HealthConnectManager.kt there are two functions getChangesToken() and getChanges(), we will add Differential Changes APIs to these functions to get data changes.

Initial changes token setup

The data changes are retrieved from Health Connect only when your app requests them with a changes token. The changes token represents the point in commit history from which differential data will be taken.

To get a changes token, send a ChangesTokenRequest with a set of data types you want to track the data changes. Keep the token and use it when you want to retrieve any updates from Health Connect.

  /**
   * TODO: Obtains a changes token for the specified record types.
   */
  suspend fun getChangesToken(): String {
    return healthConnectClient.getChangesToken(
      ChangesTokenRequest(
        setOf(
          ExerciseSessionRecord::class,
          StepsRecord::class,
          TotalCaloriesBurnedRecord::class,
          HeartRateRecord::class,
          WeightRecord::class
        )
      )
    )
  }

Data update with changes token

When you want to get changes from the last time your app synced with Health Connect, use the changes token you got earlier and send a getChanges call with the token. The ChangesResponse will have a list of observed UpsertionChange and DeletionChange from Health Connect.

  /**
   * TODO: Retrieve changes from a changes token.
   */
  suspend fun getChanges(token: String): Flow<ChangesMessage> = flow {
    var nextChangesToken = token
    do {
      val response = healthConnectClient.getChanges(nextChangesToken)
      if (response.changesTokenExpired) {
        throw IOException("Changes token has expired")
      }
      emit(ChangesMessage.ChangeList(response.changes))
      nextChangesToken = response.nextChangesToken
    } while (response.hasMore)
    emit(ChangesMessage.NoMoreChanges(nextChangesToken))
  }

Now let's run the app and go to the Changes screen. First enable the Track changes toggle button to get a changes token. Then insert weight or exercise sessions from Toolbox or from the codelab app. Go back to the Changes screen and click on the Get new changes button, you should be able to see upsertion changes now.

f3aded8ae5487e9c.png 437d69e3e000ce81.png

8. Aggregate data

Health Connect also provides aggregated data through aggregate APIs. The following examples show you how to get cumulative and statistical data from Health Connect.

Use healthConnectClient.aggregate to send AggregateRequest. In the aggregate request, specify a set of aggregate metrics and the time range you want to get. For example, the cumulative data like ExerciseSessionRecord.ACTIVE_TIME_TOTAL and StepsRecord.COUNT_TOTAL, the statistical data like WeightRecord.WEIGHT_AVG, HeartRateRecord.BPM_MAX, and HeartRateRecord.BPM_MIN.

    /**
     * TODO: Returns the weekly average of [WeightRecord]s.
     */
    suspend fun computeWeeklyAverage(start: Instant, end: Instant): Mass? {
        val request = AggregateRequest(
            metrics = setOf(WeightRecord.WEIGHT_AVG),
            timeRangeFilter = TimeRangeFilter.between(start, end)
        )
        val response = healthConnectClient.aggregate(request)
        return response[WeightRecord.WEIGHT_AVG]
    }

This example shows how to get associated aggregation data for a specific exercise session. First read a record using healthConnectClient.readRecord with a uid. Then use the startTime and the endTime of the exercise session as time range and the dataOrigin as filters to read associated aggregations.

  /**
   * TODO: Reads aggregated data and raw data for selected data types, for a given [ExerciseSessionRecord].
   */
  suspend fun readAssociatedSessionData(
      uid: String,
  ): ExerciseSessionData {
    val exerciseSession = healthConnectClient.readRecord(ExerciseSessionRecord::class, uid)
    // Use the start time and end time from the session, for reading raw and aggregate data.
    val timeRangeFilter = TimeRangeFilter.between(
      startTime = exerciseSession.record.startTime,
      endTime = exerciseSession.record.endTime
    )
    val aggregateDataTypes = setOf(
      ExerciseSessionRecord.ACTIVE_TIME_TOTAL,
      StepsRecord.COUNT_TOTAL,
      TotalCaloriesBurnedRecord.ENERGY_TOTAL,
      HeartRateRecord.BPM_AVG,
      HeartRateRecord.BPM_MAX,
      HeartRateRecord.BPM_MIN,
    )
    // Limit the data read to just the application that wrote the session. This may or may not
    // be desirable depending on the use case: In some cases, it may be useful to combine with
    // data written by other apps.
    val dataOriginFilter = setOf(exerciseSession.record.metadata.dataOrigin)
    val aggregateRequest = AggregateRequest(
      metrics = aggregateDataTypes,
      timeRangeFilter = timeRangeFilter,
      dataOriginFilter = dataOriginFilter
    )
    val aggregateData = healthConnectClient.aggregate(aggregateRequest)
    val heartRateData = readData<HeartRateRecord>(timeRangeFilter, dataOriginFilter)

    return ExerciseSessionData(
      uid = uid,
      totalActiveTime = aggregateData[ExerciseSessionRecord.ACTIVE_TIME_TOTAL],
      totalSteps = aggregateData[StepsRecord.COUNT_TOTAL],
      totalEnergyBurned = aggregateData[TotalCaloriesBurnedRecord.ENERGY_TOTAL],
      minHeartRate = aggregateData[HeartRateRecord.BPM_MIN],
      maxHeartRate = aggregateData[HeartRateRecord.BPM_MAX],
      avgHeartRate = aggregateData[HeartRateRecord.BPM_AVG],
      heartRateSeries = heartRateData,
    )
  }

Now let's run the app and check if you can see the average weight on the Record weight screen. Also, to see the detailed data of an exercise session, open the Exercise sessions screen and then tap on one of the exercise session records.

af1fe646159d6a60.png

9. Congratulations

Congratulations, you've successfully built your first Health Connect integrated health & fitness app.

The app can declare permissions and request user permissions on data types used in the app. It can also read and write data from the Health Connect data storage. You've also learned how to use the Health Connect Toolbox to support your app development by creating mock data in the Health Connect data storage.

You now know the key steps required to make your health & fitness app a part of the Health Connect ecosystem.

Further reading