Health Connect Android API

Learn how to use the Health Connect Android API to create an app that reads and writes health data and manages health permissions. By Zahid Rasheed.

Login to leave a rating/review
Download materials
Save for later
Share

Health Connect is an Android API and Platform. For developers, it provides a single interface for handling users’ health data. For users, it’s a central place to get an overview of health data and permissions granted to other apps.

In this tutorial, you’ll learn about Health Connect by making a sample app — fitLogger. In doing so, you’ll learn the following in the context of the Health Connect Android API:

  • Handling permissions
  • Writing data
  • Reading data

Getting Started

Setting up an Emulator to Test the Health Connect API

Unless you’re using a real device to build and run this app, you’ll need an emulator with Play Store support. To create an emulator, go to Tools ▸ Device Manager and click Create device. From the list of devices, choose a device with an icon in the Play Store column.

A screenshot of AVD Manager

During the next steps select a system image, choose an AVD name and click Finish.

Downloading and Exploring the Starter Project

Use the Download Materials button at the top or bottom of this tutorial to download the starter project. Open the project in Android Studio Bumblebee (2021.1.1) or later. Go to Run ▸ Run ´app´.

A screenshot of the fitLogger sample application for Android Health Connect

To keep the focus on Health Connect, the required layout for this tutorial is created in the activity_main.xml file. You’ll connect this layout with the actual functionality to read and write the health data.

Introducing Health Connect

Many health and fitness apps run on Android. Google Fit is a similar app made by Google. These apps can collect a lot of data and handle permissions. Users can use one app to store health information and another for fitness tracking. With data spread among several apps, users must go into many apps to get an overview. Also, privacy can be a worry when data is on several apps.

Health Connect is an API and a platform. As an API, it helps developers use a single interface. As a platform, it helps users have an overview of everything in one place.

A diagram showing how Android Health Connect works as both a platform and an API

Health Connect provides the following features to developers and users:

Privacy Controls

Android OS ensures client apps request permissions before accessing data or hardware on the device. Health Connect provides built-in permission controls. Developers request permissions through the API, and users can see the Health Connect app to review permission requests and grant or deny permissions.

Users can use the Health Connect app to determine which apps have permission to access health data. If needed, they can revoke those permissions through the app. Health Connect ensures that client apps can only read data when the app is running in the foreground.

Storage

Health Connect provides on-device storage, so all the data is in a central place. As a developer, you can save the data using the API and retrieve the data stored by another app.

A user can use the platform to get an overview of health and fitness data. The platform can also delete data.

Data Types

Health Connect offers an extensive range of data types to let users track types of health- and fitness-related data. Various apps can contribute or consume data in the Android ecosystem by having unified data types.

Key data categories:

  • Activity: This captures any activity by a user, including running, swimming, sleeping and meditation.
  • Body Measurement: This is common data related to the body, including height, weight and BMR (Basal Metabolic Rate).
  • Cycle Tracking: Data recorded here include menstrual cycles and other related data points.
  • Nutrition: Hydration and nutrition data types, including water, calories, sugar and magnesium.
  • Sleep: Interval data related to a user’s length and type of sleep.
  • Vitals: Essential information about the user’s general health, including blood glucose, body temperature and blood oxygen saturation is recorded under this category.

Health Connect APK

The Health Connect APK needs to be available on the client’s device. It contains the Permissions Management and Data Management components. It will handle requests sent by any application using the Health Connect SDK.

Open the Play Store on your device or emulator. If you’re using a new emulator, sign into your Google account. Then, search for Health Connect by Android and install the app.

An animation showing how to install Android Health Connect application from the Google Play Store

Handling Dependencies

Now that you have an overview of Health Connect, it’s coding time!

Add the following dependency at the bottom of the module’s build.gradle file:

implementation 'androidx.health:health-connect-client:1.0.0-alpha03'

By including this SDK in your application, you can use the Health Connect API. Click the build button to download dependencies and recompile the project.

Handling Permissions and Privacy Policy

Before you can read or write data, you need to declare all the permissions your app will use. Create a new resource file named health_permissions.xml under res/values. Add the following permissions:

<resources>
 <array name="health_permissions">
   <item>androidx.health.permission.Steps.READ</item>
   <item>androidx.health.permission.Steps.WRITE</item>
   <item>androidx.health.permission.TotalCaloriesBurned.READ</item>
   <item>androidx.health.permission.TotalCaloriesBurned.WRITE</item>
 </array>
</resources>

Each item represents a permission your app will use.

Health Connect needs the client app to define the policy on handling privacy and data.

Go to File ▸ New ▸ Activity ▸ Empty Activity. Enter PrivacyPolicyActivity for Activity Name and click Finish.

Open activity_privacy_policy.xml from the layout directory and add the following code inside the ConstraintLayout tag:

<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:textSize="16sp"
    android:layout_margin="24dp"
    android:text="@string/privacy_policy" />

Add the following in the strings.xml file:

<string name="privacy_policy">Lorem ipsum dolor sit amet, consectetur adipiscing elit....</string>

Now, in AndroidManifest.xml, replace the auto-generated element for PrivacyPolicyActivity with the following:

<activity
 android:exported="true"
 android:name=".PrivacyPolicyActivity">
 <intent-filter>
   <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE"/>
 </intent-filter>
 <meta-data android:name="health_permissions" android:resource="@array/health_permissions" />
</activity>

Here’s what’s happening:

  1. You created a new activity PrivacyPolicyActivity.
  2. In the layout file for the activity, you defined a dummy privacy policy.
  3. You then declared the activity in the manifest so your app can handle this intent and display a privacy policy explaining how users’ data is handled.

Now, add the following functions after onCreate within MainActivity.kt:

private fun checkPermissionsAndRun() {
  // 1
  val client = HealthConnectClient.getOrCreate(this)

  // 2
  val permissionsSet = setOf(
    Permission.createWritePermission(StepsRecord::class),
    Permission.createReadPermission(StepsRecord::class),
    Permission.createWritePermission(TotalCaloriesBurnedRecord::class),
    Permission.createReadPermission(TotalCaloriesBurnedRecord::class),
  )

  // 3
  // Create the permissions launcher.
  val requestPermissionActivityContract = client
    .permissionController
    .createRequestPermissionActivityContract()

  val requestPermissions = registerForActivityResult(
    requestPermissionActivityContract
  ) { granted ->
    if (granted.containsAll(permissionsSet)) {
      // Permissions successfully granted
      lifecycleScope.launch {
        onPermissionAvailable(client)
      }
    } else {
      Toast.makeText(
        this, "Permissions not granted", Toast.LENGTH_SHORT
      ).show()
    }
  }

  // 4
  lifecycleScope.launch {
    val granted = client.permissionController
      .getGrantedPermissions(permissionsSet)
    if (granted.containsAll(permissionsSet)) {
      // Permissions already granted
      onPermissionAvailable(client)
    } else {
      // Permissions not granted, request permissions.
      requestPermissions.launch(permissionsSet)
    }
  }
}

private suspend fun onPermissionAvailable(client: HealthConnectClient) {
 // todo: read data
}
Note: While importing dependencies, there are many import options for Permission. Please choose androidx.health.connect.client.permission.

Here’s what’s happening in checkPermissionsAndRun:

  1. You first of all get a hold of a HealthConnectClient object. getOrCreate creates a new instance or returns an existing one if it’s available.
  2. Create a set of permissions with required data types to request permissions within your application. Permissions in these sets need to be declared in the health_permissions resource array.
  3. Create a request permission launcher. On launch, users will be prompted for permissions declared in the health_permissions resource array.
  4. Finally, through HealthConnectClient, you check whether you have the required permissions. If permissions aren’t available, you request permissions through requestPermissions launcher.

onPermissionAvailable is called when you have all required permissions. At this point, it’s possible to read or write data.

Add the following after the closing application tag within AndroidManifest.xml:

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

This checks whether Health Connect APK is installed.

Finally, inside onCreate in MainActivity.kt, replace // Your code with the following code:

if (HealthConnectClient.isAvailable(this)) {
    // Health Connect is available
    checkPermissionsAndRun()
 } else {
    Toast.makeText(
      this, "Health Connect is not available", Toast.LENGTH_SHORT
    ).show()
}

Here’s what’s happening:

  • Through HealthConnectClient, you check whether Health Connect is available.
  • If it is available, you check required permissions.

You show a Toast to the user, if Health Connect isn’t available.

That’s it! You’ve integrated Health Connect in the app and can request all required permissions. Give it a try by running the app.

An animation showing how to grant permissions in Android Health Connect

Writing Data

Inserting Records

Add the following code after onPermissionAvailable.

private fun insertData(client: HealthConnectClient, steps: Long, caloriesBurned: Double) {
  // 1
  val startTime = ZonedDateTime.now().minusSeconds(1).toInstant()
  val endTime = ZonedDateTime.now().toInstant()

  // 2
  val records = listOf(
   StepsRecord(
     count = steps,
     startTime = startTime,
     endTime = endTime,
     startZoneOffset = null,
     endZoneOffset = null,
   ),
   TotalCaloriesBurnedRecord(
     energy = Energy.calories(caloriesBurned),
     startTime = startTime,
     endTime = endTime,
     startZoneOffset = null,
     endZoneOffset = null,
   )
  )

  // 3
  lifecycleScope.launch {
    val insertRecords = client.insertRecords(records)

    if (insertRecords.recordUidsList.isNotEmpty()) {
      runOnUiThread{
          Toast.makeText(
            this@MainActivity,
            "Records inserted successfully",
            Toast.LENGTH_SHORT
          ).show()
        }
      }
    }
}

With this update,

  1. You’re creating a time range with a start and end. You record the data in a small interval. This way you can insert the data multiple times in a day.
  2. Followed by another list that contains StepsRecord and TotalCaloriesBurnedRecord records.
  3. Then finally, you insert the created record through the HealthConnectClient instance. recordUidsList contains the uids of inserted records. When the list isn’t empty, you’re showing a success message to the user.
Note: For the sake of simplicity, keep ZoneOffset null. You can read more about ZoneOffset.

Now, at the end of onCreate in the MainActivity.kt, add the following code:

val stepsEditText = findViewById<EditText>(R.id.stepsEditText)
val caloriesEditText = findViewById<EditText>(R.id.caloriesEditText)

findViewById<Button>(R.id.submit).setOnClickListener {
  val steps = stepsEditText.text.toString().toLong()
  val calories = caloriesEditText.text.toString().toDouble()

  val client = HealthConnectClient.getOrCreate(this)
  insertData(client, steps, calories)

  // clear input fields after insertion and close the keyboard
  stepsEditText.text.clear()
  caloriesEditText.text.clear()
  caloriesEditText.onEditorAction(EditorInfo.IME_ACTION_DONE)
}

In the code above, when a user taps Button, you read input values and save them with insertData(). You then clear input fields and close the keyboard.

Build and Run

That’s all you need to do to write data through the Health Connect API. Run the project, input values and tap the button.

An animation to demo the input of steps and calories health data

Reading Data

You can read data in two ways using HealthConnectClient.

  • ReadRecordsRequest: Read records determined by time range and other filters. You’ll use this method to read the daily steps count and calories intake.
  • AggregateRequest: Read aggregations for a given AggregateMetric. You’ll use this method to read monthly step counts and caloric intakes.

Reading Data through a ReadRecordsRequest

In MainActivity.kt, add the following after insertData():

private suspend fun readDailyRecords(client: HealthConnectClient) {
  // 1
  val today = ZonedDateTime.now()
  val startOfDay = today.truncatedTo(ChronoUnit.DAYS)
  val timeRangeFilter = TimeRangeFilter.between(
    startOfDay.toLocalDateTime(),
    today.toLocalDateTime()
  )

  // 2
  val stepsRecordRequest = ReadRecordsRequest(StepsRecord::class, timeRangeFilter)
  val numberOfStepsToday = client.readRecords(stepsRecordRequest)
    .records
    .sumOf { it.count }
  val stepsTextView = findViewById<TextView>(R.id.stepsTodayValue)
  stepsTextView.text = numberOfStepsToday.toString()

  // 3
  val caloriesRecordRequest = ReadRecordsRequest(
    TotalCaloriesBurnedRecord::class,
    timeRangeFilter
  )
  val caloriesBurnedToday = client.readRecords(caloriesRecordRequest)
    .records
    .sumOf { it.energy.inCalories }
  val caloriesTextView = findViewById<TextView>(R.id.caloriesTodayValue)
  caloriesTextView.text = caloriesBurnedToday.toString()
}

And now the breakdown:

  1. You create a TimeRangeFilter from the start of the day until now.
  2. You then create a ReadRecordRequest for StepsRecord. Through the HealthConnectClient instance, you read records and get the sum. You get the sum because there can be many records for steps taken today. Finally, you display the daily steps count.
  3. This is the same as Step 2, but the ReadRecordsRequest is for TotalCaloriesBurnedRecord.

Reading Data through an AggregateRequest

Add the following method at the bottom of MainActivity:

private suspend fun readAggregatedData(client: HealthConnectClient) {
  // 1
  val today = ZonedDateTime.now()
  val startOfDayOfThisMonth = today.withDayOfMonth(1)
    .truncatedTo(ChronoUnit.DAYS)
  val elapsedDaysInMonth = Duration.between(startOfDayOfThisMonth, today)
    .toDays() + 1
  val timeRangeFilter = TimeRangeFilter.between(
    startOfDayOfThisMonth.toInstant(),
    today.toInstant()
  )

  // 2
  val data = client.aggregate(
    AggregateRequest(
      metrics = setOf(
        StepsRecord.COUNT_TOTAL,
        TotalCaloriesBurnedRecord.ENERGY_TOTAL
      ),
      timeRangeFilter = timeRangeFilter,
    )
  )

  // 3
  val steps = data[StepsRecord.COUNT_TOTAL] ?: 0
  val averageSteps = steps / elapsedDaysInMonth
  val stepsAverageTextView = findViewById<TextView>(R.id.stepsAverageValue)
  stepsAverageTextView.text = averageSteps.toString()

  // 4
  val caloriesBurned = data[TotalCaloriesBurnedRecord.ENERGY_TOTAL]
      ?.inCalories ?: 0.0
  val averageCaloriesBurned = caloriesBurned / elapsedDaysInMonth
  val caloriesAverageTextView = findViewById<TextView>(
    R.id.caloriesAverageValue
  )
  caloriesAverageTextView.text = getString(R.string.format_calories_average)
      .format(averageCaloriesBurned)
}
Note: While importing dependencies, there are many import options for Duration. Please choose java.time.

Here’s what’s happening:

  1. You’re creating a TimeRangeFilter from the start of the month until now. Also, you’re calculating the days elapsed in the month so you can calculate the average.
  2. You’re making an aggregate request through HealthConnectClient with a set of measurements to get the total steps and calories within a specific time range. The benefit of aggregated data is it includes basic aggregations or aggregating data into buckets.
  3. You’re calculating averageSteps and updating the layout.
  4. You’re calculating averageCaloriesBurned and updating the layout. For a better UI, you’re rounding the calorie values up to two decimals.

Add the following in the strings.xml file.

<string name="format_calories_average">%.2f</string>

You’re almost there. You now have functions that read data, but you need to connect a few final dots.

Add another method in the MainActivity.kt:

private suspend fun readData(client: HealthConnectClient) {
  readDailyRecords(client)
  readAggregatedData(client)
}

This helper function commands both functions you created above to read data.

Now, inside onPermissionAvailable, replace // todo: read data with the following.

readData(client)

By doing so, you can read the data and update the layout as soon as you have permissions available.

Finally, add the following at the end of the lifecycleScope.launch { block, inside insertData:

readData(client)

This will ensure you show updated data after a user inserts new data.

Build and Run

You’re done! Build and run the app to see the result. You can now write data and also see your existing data.

An animation to demo displaying the total steps and calories health data entered today and the average this month

Where to Go From Here?

Congratulations! You’ve learned about Health Connect by making a simple app. Now you have one less excuse to get fitter and healthier. Use the Download Materials button at the top or bottom of this article to download the final project.

You can take a few steps to improve your app: