Using Health Connect in the Android Mobile SDK

The Validic Inform Android Mobile SDK offers native mobile libraries (currently native Android) which you can implement into your mobile project to power data syncing with the Android Health Connect health data aggregation platform.

The Validic Inform Android Mobile SDK currently supports Android Health Connect for native Android, therefore this document is currently scoped to the Android Health Connect integration. We will be adding more integrations and platforms to the Inform Android Mobile SDK over time and documentation will be added to cover the new integrations and platforms as they are available.

Android Health Connect allows health and fitness apps to store and share the same on-device data, within a unified ecosystem. It also offers a single place for users to control which apps can read and write health and fitness data.

Health Connect combines data from a variety of other fitness and health apps, including Google Fit and Samsung Health.


Installation

The Inform Mobile library is hosted in Artifactory. It is divided into separate feature modules.

The Shared module (validicmobile-shared) is required. Other modules should be added to your project based on the functionality you wish to support in your app.

Since there is currently only one other module in the Inform Android SDK (aggregator-health-connect), both modules are currently required.

Maven

The Distribution zip contains a local Maven repository, validic-repository that can be used to add the library and its dependencies to your project's classpath.

In your app/build.gradle file add the following

repositories {
    mavenCentral()
    google()
  // Validic Support can provide you with credentials if you need them
  	maven {
    	url: "https://validic.jfrog.io/artifactory/mobile-release/"
      credentials: {
       	username: username
        password: password
      }
    }
  // or 
    maven {
        // Android Maven Distribution folder from zip. Change to point to folder location
        url "../../validic-repository" 
    }
}

dependencies{
...
  implementation 'com.validic.mobile:validicmobile-shared:+'
  implementation 'com.validic.mobile:aggregator-health-connect:+'
}

Logging

Logging can be enabled using the static methods in the ValidicLog class.

ValidicLog.globalLoggingEnabled(true);

By default, nothing is logged. To start logging update the LogLevel to the appropriate level.

ValidicLog.setLogLevel(ValidicLog.DEBUG);

or set your own logging instance

ValidicLog.setLogger(new Logger(){
       @Override
       void log(int level, String tag, String msg){
             Timber.log(level, msg);
        }
}

Session

SessionV2 a singleton object and must be accessed by its getInstance(context) method. The Session instance stores a Validic User and all pending InformRecord uploads. A valid session is required in order to use any of the modules in the Validic SDK.

A Validic user can be provisioned using the Validic API. Further documentation is available for the Inform API.

You will need to provision the user on your server. The mobile app could then authenticate to the server and retrieve the needed Validic credentials.

To use an existing Validic User, create a User object and provide it to the startSessionWithUser(User) method.

User user = new User("Your Organization ID",
                     "A Validic ID",
                     "A Validic User Mobile Access Token");

Session.getInstance(context).startSessionWithUser(user);

User data is persisted between app launches but is deleted if endSession() is called.

Session.getInstance().endSession();

Record types

In the Inform Android Mobile for Health Connect, there are five different subclasses of InformRecord that will be collected by the SDK:

  • Summaries – comprised of a user’s activities that occur regularly throughout the day, without the specific goal of exercise, for example calories burned, steps taken, etc. These activities are aggregate throughout the day.
  • Workouts – comprised of a user’s activities that are undertaken with the express purpose of exercising. These activities have a defined duration.
  • Sleep – comprised of a user’s measurements related to the length of time spent in various sleep cycles, as well as number of times woken during the night.
  • Measurements – comprised of a user’s body measurements and vitals, for example weight, body fat, blood pressure, and spo2.
  • Nutrition – comprised of a user’s nutrition consumption, for example calories_consumed, carbohydrates, dietary fiber, and sodium.

Record Identifiers

The Validic Mobile SDK uses log_id as the unique identifier for a given record. These IDs are created by the mobile SDK before submission to the Inform server.

The Inform server also assigns an id for each unique record. It is recommended to use the server-side id to identify and/or de-duplicate records post-submission.

Listening for InformRecord Upload

When Session uploads an InformRecord it will notify the stored SessionListener. To receive these notifications, register a SessionListener by calling setSessionListener(SessionListener):

SessionV2.Listener listener = new SessionV2.Listener() {
    @Override
    public void onResourceResponse(@NonNull InformRecord record, @Nullable Exception error) {
        // record will contain the server `id` if successfull and error will be null
    }

    @Override
    public void onUserSessionEnded(@NonNull User, @NonNull List<InformRecord> records) {
      // User Session Was ended with any unuploaded records that have been removed from storage
    }
};

SessionV2.getInstance(context).setSessionListener(listener)

If a response is received from the server with a success (2xx status) or a 401 or 403 status, the record will be removed from storage after emitting the result.

Health Connect


This project integrates Health Connect with the Validic Mobile Aggregator to passively collect data on behalf of an end user. Validic ensures that our Mobile SDK makes use of the minimal scopes needed for our supported data types.

Please be aware that your app will need to be released on the Play Store in order for Google to review it prior to using the Health Connect data sync. However, you do not need to publish your app in order to test Health Connect.

Please review Google's guide to get started planning your request to Google for Health Connect data access.

Availability

Accessing Health Connect depends on what OS version the end user is running. Check if Health Connect is available on the device before attempting to access Health Data.

Android 14

Health Connect is a system app and is therefore available on every Android device running Android >= Android 14.

Android 13 and Below

For devices running Android <= Android 14 Health Connect is available as an application on the Play Store.

val status = ValidicHealthConnect.getPlatformStatus()  
when (status) {  
  HealthConnectClient.SDK_UNAVAILABLE -> {  
    // No viable integration  
  }  
  HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> {  
    //Redirect to package installer to find a provider, for example:  
    val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"  
    context.startActivity(  
      Intent(Intent.ACTION_VIEW).apply {  
        setPackage("com.android.vending")  
        data = Uri.parse(uriString)  
        putExtra("overlay", true)  
        putExtra("callerId", context.packageName)  
      }  
    )  
  }  
  else -> {  
    // Good to go!  
  }  
}

Accessing Sensitive Data

Data stored in Health Connect is considered sensitive. Accessing end user data requires that the user grant permission to each data type before data can be read. To get started add the permission for each data type that you want to use in your application's AndroidManifest.xml. A full list of datatypes and the necessary permissions are here.

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

  <application>
  ...
  </application>
</manifest>

Privacy policy

The Permissions dialog for Health Connect displays a link that must be handled by your application to display a privacy policy when the link is clicked. Register an Activity to handle the ACTION_SHOW_PERMISSIONS_RATIONALE intent.

<application>
  ...
  <!-- For supported versions through Android 13, create an activity to show the rationale
       of Health Connect permissions once users click the privacy policy link. -->
  <activity
      android:name=".PermissionsRationaleActivity"
      android:exported="true">
    <intent-filter>
      <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
    </intent-filter>
  </activity>

  <!-- For versions starting Android 14, create an activity alias to show the rationale
       of Health Connect permissions once users click the privacy policy link. -->

  <activity-alias
      android:name="ViewPermissionUsageActivity"
      android:exported="true"
      android:targetActivity=".PermissionsRationaleActivity"
      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>  
  ...  
</application>

Implementation

Please review the class documentation for more details on the functions available in ValidicHealthConnect and HealthConnectInitializer.

Request permissions

Before reading or subscribing to data from Health Connect the end user must grant permissions for each Health Connect Record Type.

Get previously granted permissions

Checking the permission status for a Health Connect Record Type will indicate if it is possible to read and subscribe to data changes for a Health Connect Record.

viewLifecycleScope.launch {
  val canReadSteps = ValidicHealthConnect.getInstance(context).hasReadPermission(StepsRecord::class)
  if (!canReadSteps) {
    // Request Permission for steps
  }
}

Health Connect permissions are documented in Google's guide.

To check if permissions have already been granted the hasReadPermission method can be used.

Request new permissions

Health Connect permissions are documented in Google's guide.

To request permission from the end user to authorize data collection, you must subscribe to record types. The Validic Health Connect integration provides a convenience method to create an ActivityResultContract in your Activity or Fragment that can be used for requesting access from the end user.

val healthPermissionLauncher = registerForActivityResult(ValidicHealthConnect.createPermissionContract()) {
  it.forEach { p ->
    Log.i("Permission", "${HealthPermission.getReadPermission(p.key)} - ${p.value}")
  }
}

healthPermissionLauncher.launch(setOf(StepsRecord::class))

When subscribing to data changes for Health Connect Record Types the ACTIVITY_RECOGNITION permission must also be granted.

Subscribing to datatypes can be done via ValidicHealthConnect.subscribe(KClass<out Record>) method.

Android 15: If subscribed to a data type on Android 15 and the ACTIVITY_RECOGNITION has not been granted then the Foreground service will stop due to Foreground restrictions introduced in Android 15.

scope.launch {  
  try {  
    ValidicHealthConnect.getInstance(context).subscribe(StepsRecord::class)  
  } catch (e:Exception) {  
    when (e) {  
      is SecurityException -> Log.e("Permission Error", "Permission not granted for type")  
      is IllegalStateException -> Log.e("Illegal State", "Health Connect is not available")  
      else -> Log.e("Error", e)  
    }  
  }  
}

Manage subscriptions

After Permissions have been granted for a record type, ValidicHealthConnect can be used to add subscriptions to data change events from Health Connect.

Data will automatically will queued for submission to the Validic Inform Mobile API when internet is available on the phone. A foreground service must be used for polling data from Health Connect. Health Connect does not sync data in the background, unless the user is on Android 15+.

📘

Android 15 Updates to Health Connect

Google has released a Health Connect improvement in Android 15 to allow apps to access data in the background. Prior to Android 15 this was not possible and the app must run as a foreground service or wait until it is opened to start syncing with Health Connect.

In Android 15 Google added a new Health Connect background read permission, which the Inform SDK will request when you request permissions from the user. If the user grants the required permission, then the SDK will read Health Connect data in the background every 15 minutes.

For lower versions of Android, to set a android.app.Notification for connecting to Health connect see subscriptionConfiguration.

To unregister from receiving data update events use the ValidicHealthConnect to unsubscribe.

You can get a list of currently subscribed record types using currentSubscriptions in ValidicHealthConnect.

Currently Supported Record Types

Retrieve the list of currently subscribed Health Connect Record Types:

scope.launch {
  val current = ValidicHealthConnect.getInstance(context).getCurrentSubscriptions()
  Log.i("Current Subscription", current) //  ["StepsRecord"]
}

Refer to the following table for more details on the supported record types for Health Connect:

Health Connect Type (data type)Inform ResourceInform Metric
Stepssummariessteps
ActiveCaloriesBurnedsummariesactive_energy_burned
TotalCaloriesBurnedsummariesenergy_burned
Distancesummariesdistance
FloorsClimbedsummariesfloors_climbed
BasalMetabolicRatesummariesbasal_energy_burned
HeartRate (BPM_AVG)summariesavg_heart_rate
HeartRate (BPM_MAX)summariesmax_heart_rate
HeartRate (BPM_MIN)summariesmin_heart_rate
ExerciseSession (EXERCISE_DURATION_TOTAL)summariesactive_duration
Weightmeasurementsbody_weight
BloodGlucosemeasurementsblood_glucose
BloodPressure (diastolic)measurementsdiastolic
BloodPressure (systolic)measurementssystolic
OxygenSaturationmeasurementsspo2
BodyTemperaturemeasurementsbody_temperature
BodyFatmeasurementsbody_fat
BodyWaterMassmeasurementsbody_water_mass
BoneMassmeasurementsbody_bone_mass
LeanBodyMassmeasurementsbody_lean_mass
Heightmeasurementsbody_height
ExerciseSession (EXERCISE_DURATION_TOTAL)workoutsworkout_duration
SleepSession (SLEEP_DURATION_TOTAL)sleepsleep_duration
SleepSession (STAGE_TYPE_LIGHT)sleeplight_sleep
SleepSession (STAGE_TYPE_DEEP)sleepdeep_sleep
SleepSession (STAGE_TYPE_REM)sleeprem_sleep
Nutrition (calcium)nutritioncalcium
Nutrition (totalCarbohydrate)nutritioncarbohydrate
Nutrition (cholesterol)nutritioncholesterol
Nutrition (dietaryFiber)nutritiondietary_fiber
Nutrition (energy)nutritionenergy_consumed
Nutrition (totalFat)nutritionfat
Nutrition (protein)nutritionprotein
Nutrition (saturatedFat)nutritionsaturated_fat
Nutrition (sodium)nutritionsodium
Nutrition (sugar)nutritionsugars
Nutrition (unsaturatedFat)nutritionunsaturated_fat

InformRecords collected are automatically submitted to the Validic Inform Mobile API and a summary of records processed will be sent to any listener registered using ValidicHealthConnect.

Fetch historical data

The Health Connect integration supports retrieving 30 days of historical step data.

Reading Historical data and submitting it to the Validic API can be done using ValidicHealthConnect. Each Inform Record Type has an associated method to request data be fetched from the Health Connect data store. Each method returns a Summary of the number of records that were gathered and collected and submitted to the Validic Mobile API.

Note that it is required that this method is used while the app is in the Foreground or it will throw an exception.

Daily Activity Summary

A Users Daily aggregate of Activity data can be read using the Summary Inform Record Type.

lifecycleScope.launch {
  try {
    // Fetch the last 7 days of data
    val summary = ValidicHealthConnect.getInstance(context).readDailySummary(LocalDate.now().minusDays(6), LocalDate.now())
    Log.i("Success", "Collected ${summary[InformRecordType.summary]} records from the last 7 days from Health Connect")
  } catch (e: Exception) {
    Log.e("Error", e)
  }
}

Measurements

A user's instantaneous Health Connect Record Types can be read using the Measurement Inform Record Type.

lifecycleScope.launch {
  try {
    // Fetch the last 7 days of data
    val summary = ValidicHealthConnect.getInstance(context).readMeasurement(WeightRecord::class, LocalDate.now().minusDays(6), LocalDate.now())
    Log.i("Success", "Collected ${summary[InformRecordType.measurement]} records from the last 7 days from Health Connect"
  } catch (e: Exception) {
    Log.e("Error", e)
  }
}

Nutrition Summary

A user's Daily Nutrition Summary can be read using the Nutrition Inform Record Type.

lifecycleScope.launch {  
  try {  
    // Fetch the last 7 days of data  
    val summary = ValidicHealthConnect.getInstance(context).readNutrition(LocalDate.now().minusDays(6), LocalDate.now())  
    Log.i("Success", "Collected ${summary[InformRecordType.nutrition]} records from the last 7 days from Health Connect"  
  } catch (e: Exception) {  
    Log.e("Error", e)  
  }  
}

Sleep

A user's sleeping data can be read using the Sleep Inform Record Type.

lifecycleScope.launch {  
  try {  
    // Fetch the last 7 days of data  
    val sleep = ValidicHealthConnect.getInstance(context).readSleep(LocalDate.now().minusDays(6), LocalDate.now())  
    Log.i("Success", "Collected ${summary[InformRecordType.sleep]} records from the last 7 days from Health Connect"  
  } catch (e: Exception) {  
    Log.e("Error", e)  
  }  
}

Workouts

A user's exercise and fitness data can be read using the Workout Inform Record Type.

lifecycleScope.launch {  
  try {  
    // Fetch the last 7 days of data  
    val summary = ValidicHealthConnect.getInstance(context).readWorkouts(LocalDate.now().minusDays(6), LocalDate.now())  
    Log.i("Success", "Collected ${summary[InformRecordType.workout]} records from the last 7 days from Health Connect"  
  } catch (e: Exception) {  
    Log.e("Error", e)  
  }  
}

Logout

To remove all subscriptions and log an end user out simply call disconnect.

Listening to Health Connect API Exceptions

Errors thrown by the Health Connect Platform are Logged and then re-thrown. More information on those errors can be found here.