Build a health app with steps from Samsung Health and its connected wearables


Objective

Create a step counter application for Android devices, utilizing the Samsung Health Data SDK to read steps data collected by the Samsung Health app.

Overview

Samsung Health offers various features to monitor user health data such as their daily step activity. With the Samsung Health Data SDK, Android applications can easily access collected data, including steps recorded over a specific period or from a certain device.

You can retrieve steps data collected by Samsung Health, obtain the total number of steps taken within the day, and the total number of steps per hour, and apply local time filters to refine your queries effectively.

Set up your environment

You will need the following:

  • Android Studio (latest version recommended)
  • Java SE Development Kit (JDK) 17 or later
  • Android mobile device compatible with the latest Samsung Health version

Sample Code

Here is a sample code for you to start coding in this Code Lab. Download it and start your learning experience!

Health Data Steps Sample Code
(573.9 KB)

Set up your Android device

Click on the following links to set up your Android device:

  1. Enable developer options
  2. Run apps on a hardware device

Activate Samsung Health's Developer Mode

To enable the Developer mode in the Samsung Health app, follow these steps:

  1. Go to Settings > About Samsung Health.
  2. Then, tap the version number quickly 10 times or more. If you are successful, the Developer mode (new) button is shown.

  1. Tap Developer mode (new) and choose On.

Now, you can test your app with Samsung Health.

Start your project

  1. In Android Studio click Open to open existing project.

  1. Locate the downloaded Android project (MySteps) from the directory and click OK.

Check Gradle settings

Before using the Samsung Health Data SDK library, certain configurations are necessary. These steps are already applied in the sample code provided:

  1. The samsung-health-data-api-1.0.0b1.aar library is added to the app\libs folder, and included as a dependency in the module-level build.gradle file.

    In the same file, the gson library is also added as a dependency.
dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
    implementation(libs.gson)
} 
  1. Next, the kotlin-parcelize plugin is applied.
plugins { 
    id("kotlin-parcelize") 
} 
  1. Lastly, the following entries are also added in the gradle > libs.version.toml file.
[versions] 
gson = "2.10.1"
parcelize = “1.9.0”

[libraries] 
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }

[plugins]
parcelize = { id = “org.jetbrains.kotlin.plugin.parcelize”, version.ref = ”parcelize” }

Request steps data permissions

To read data from Samsung Health, you need to acquire proper permissions from the app user. Each health data type has its own permission. Additionally, separate permissions are required for reading and writing operations.

The user must grant the following permissions in the app:

  • STEPS for read operation

  • STEPS_GOAL for read operation

When launching the application, it is important to verify if the necessary permissions have already been granted. This can be achieved through the library function: HealthDataStore.getGrantedPermissions(permissions: Set<Permission>): Set<Permission>

Go to app > kotlin+java > com.samsung.health.mysteps.domain.

In the ArePermissionsGrantedUseCase.kt file, navigate to the Permissions object and create the permissions needed to read the steps and steps goal data from Samsung Health.

/****************************************************************************
 * [Practice 1] Create permission set to receive step data
 *
 * Make PERMISSIONS set down below contain two Permission
 * (com.samsung.android.sdk.health.data.permission.Permission) objects of types:
 *  - 'DataTypes.STEPS' of 'AccessType.READ'
 *  - 'DataTypes.STEPS_GOAL of 'AccessType.READ'
 ****************************************************************************/
object Permissions {

	//TODO 1
    val PERMISSIONS = emptySet<Permission>()
}

If the permissions are not granted, invoke an ask-for-permissions view. The special function provided by the library is called from MainActivity, where the context is an Activity's context.

val result = healthDataStore.requestPermissions(PERMISSIONS, context) 

After invoking the function, the app user sees the following pop-up upon starting the application:

If the user does not consent to read their steps data, the application displays a message explaining why this authorization is vital for the app to function properly.

Retrieve steps data from Samsung Health

Understand how to retrieve step goal

A step goal is a target number of steps set by an individual to achieve within a day. This can be set in the Samsung Health app by navigating to Steps > Settings > Set target.

Check the readLastStepGoal() function in ReadStepGoalFromTodayUseCase.kt to know how to retrieve the most recent step goal from Samsung Health.

@Throws(HealthDataException::class)
private suspend fun readLastStepGoal(): Int {
    val startDate = LocalDate.now()
    val endDate = LocalDate.now().plusDays(1)
    Log.i(TAG, "StartDate: $startDate; EndDate: $endDate")
    val readRequest = DataType.StepsGoalType.LAST
        .requestBuilder
        .setLocalDateFilter(LocalDateFilter.of(startDate, endDate))
        .build()
    val result = healthDataStore.aggregateData(readRequest)
    var stepGoal = 0
    result.dataList.forEach { data ->
        Log.i(TAG, "Step Goal: ${data.value}")

        Log.i(TAG, "data.startTime: ${data.startTime}")
        Log.i(TAG, "data.endTime: ${data.endTime}")
        data.value?.let { stepGoal = it }
    }
    return stepGoal
}

The function readLastStepGoal() retrieves the most recent step goal from Samsung Health. First, it filters the data by setting the startDate and endDate to the current date and the next day respectively using a LocalDateFilter. Next, the function builds a request using the DataType.StepsGoalType.LAST constant to retrieve the most recent value and specifies the date range using the setLocalDateFilter() method. The request is then executed by calling the aggregateData() function of the healthDataStore. Once the data is fetched, the function loops through each entry and extracts the step goal value. Finally, it returns the step goal value as the result.

Collect today's total number of steps

To verify if the user reached their daily step goal, get the number of steps taken from midnight until the current time. Perform this calculation by creating a generic function that calculates the total number of steps within a specified time frame. Then, set the start time as the beginning of today and the end time as the current timestamp.

  • TOTAL is an aggregate operation that obtains the sum of steps.

To achieve this task, use the following:

  • HealthDataStore.getGrantedPermissions(permissions: Set<Permission>)
  • Set.containsAll(elements: Collection<@UnsafeVariance E>)
AggregateRequest
It represents a request for an aggregation query over time. It is used to run aggregate operations like TOTAL and LAST for HealthDataPoint.

LocalTimeFilter
Filter with a LocalDateTime type time interval as a condition. The time interval is represented as local date time.

Companion function of (startTime: LocalDateTime?, endTime: LocalDateTime?)
Creates a LocalTimeFilter with startTime and endTime.

AggregateRequest.LocalTimeBuilder<T>
Provides a convenient and safe way to set the fields and create an AggregateRequest.

setLocalTimeFilter(localTimeFilter: LocalTimeFilter)
Sets the local time filter of the request.

In ReadStepsFromATimeRangeUseCase.kt, navigate to the function getAggregateRequestBuilder and filter today's steps.

/***************************************************************************
 * [Practice 2] - Create a read request builder to obtain steps from given 
 * time range
 * Collecting steps from a period of time is an aggregate operation which
 * sums up all the steps from that period.
 * In this exercise you need to:
 *  - create a localTimeFilter with startTime and endTime for the
 * AggregateRequest
 *  - apply the filter to the aggregateRequest
 * -------------------------------------------------------------------------
 *  - (Hint)
 *  Use LocalTimeFilter.of() to create a time filter for the request
 **************************************************************************/
fun getAggregateRequestBuilder(
    startTime: LocalDateTime,
    endTime: LocalDateTime
): AggregateRequest<Long> {
val aggregateRequest = DataType.StepsType.TOTAL.requestBuilder
    .build()
// TODO 2
return aggregateRequest
}

A list of aggregated data is received as a result of the request. In this example, it's a single-item list containing the total number of steps taken from the beginning of the day to the present moment. With the given code, you can iterate over the list and check if the value of the analyzed aggregatedData element is not null. If so, assign it to the stepCount variable. However, if the value is empty, the code returns a value of 0, indicating that no steps were recorded.

val result = healthDataStore.aggregateData(aggregateRequest)
var stepCount = 0L
result.dataList.forEach { aggregatedData ->
    aggregatedData.value?.let { stepCount = it }
}

Obtain the number of steps for each hour

After setting up the functions to fetch steps data from Samsung Health and aggregating the data to calculate the total step count, you need to obtain the total number of steps for each hour and visualize the steps the user took during every hour of the day.

To achieve this, utilize the aggregate operation (sum of steps), but this time incorporate additional filtering (grouping by hour).

AggregateRequest
It represents a request for an aggregation query over time. It is used to run aggregate operations like TOTAL and LAST on HealthDataPoint.

LocalTimeFilter
Filter with a LocalDateTime type time interval as a condition. The time interval is represented as local date time.

Companion function of (startTime: LocalDateTime?, endTime: LocalDateTime?)
Creates a LocalTimeFilter with startTime and endTime.

LocalTimeGroup
Grouped time interval with a pair of LocalTimeGroupUnit and multiplier. This means that the grouping is applied to intervals as much as multiplier in local date and time.

Companion function of (timeGroupUnit: LocalTimeGroupUnit, multiplier: Int)
Creates a LocalTimeGroup with the given timeGroupUnit and multiplier.

LocalTimeBuilder<T>
Provides a convenient and safe way to set the fields and create an AggregateRequest.

setLocalTimeFilterWithGroup(localTimeFilter: LocalTimeFilter?, localTimeGroup: LocalTimeGroup?)
Sets the local time filter with the local time group of the request.

In ReadGroupedStepsByTimeRangeUseCase.kt, navigate to the getAggregateRequestBuilder function.

Obtain the total number of steps from every hour by creating two variables: one to specify the time range using localTimeFilter.of() and another to define the grouping using LocalTimeGroup.of(). By combining these variables, you can set an aggregate request that retrieves the desired data.

/************************************************************************
 * [Practice 3] - Create an aggregate request for steps from given period 
 *  of time
 *  For this, DataType.STEPS grouped by hour is needed
 *  In this exercise you need to:
 *  - create an aggregate request, with grouping and time filters, 
 *    for filters' parameters use the method's arguments
 *  - return the request
 * ----------------------------------------------------------------------
 *  - (Hint)
 *  use setLocalTimeFilterWithGroup function to apply time and grouping 
.* filters to the request builder
 ***********************************************************************/
fun getAggregateRequestBuilder(
    startDateTime: LocalDateTime,
    endDateTime: LocalDateTime,
    localTimeGroupUnit: LocalTimeGroupUnit,
    multiplier: Int
): AggregateRequest<Long> {
val aggregateRequest = DataType.StepsType.TOTAL.requestBuilder
    .build()
// TODO 3
return aggregateRequest
}

To apply local time filtering with the given group, use the LocalTimeFilter and LocalTimeGroup classes. The LocalTimeGroup consists of a LocalTimeGroupUnit, which in this case is HOURLY, and a multiplier. You can also group by other time periods such as DAILY, WEEKLY, MONTHLY, MINUTELY, and YEARLY.

Since you want data from every hour period, use a multiplier value of 1. The returned data from the request is a list, where each item represents a grouped value. HealthDataStore only returns values for periods when the step count is greater than zero.

The code below shows that by iterating over the returned dataList and adding non-null groupedData to the output stepList, you can obtain the aggregated value of steps for each hour of the day.

val result = healthDataStore.aggregateData(aggregateRequest)

val stepList: ArrayList<GroupedData> = ArrayList()
result.dataList.forEach { aggregatedData ->
    var stepCount = 0L
    aggregatedData.value?.let { stepCount = it }
    val startTime = aggregatedData.startTime.atZone(ZoneId.systemDefault())
    val groupedData = GroupedData(stepCount, startTime.toLocalDateTime())
    stepList.add(groupedData)
}

Run unit tests

For your convenience, an additional Unit Tests package is provided. This package lets you verify your code changes even without using a physical device.

  1. Right-click on com.samsung.health.mysteps (test) and select Run 'Tests in 'com.samsung.health.mysteps'.

  1. If you completed all the tasks correctly, you can see that all the unit tests passed successfully.

Run the app

After building the APK, you can run the app on a connected device to read your steps data.

  1. Once the app starts, allow All permissions to read steps data from Samsung Health and tap Done.

  1. Afterwards, the app's main screen appears, displaying the daily summary of steps taken, target, and steps by hour. Swipe down to sync the latest data from Samsung Health.

  1. You can scroll down to Steps by hour to see the hourly breakdown.

You're done!

Congratulations! You have successfully achieved the goal of this Code Lab. Now, you can create a mobile health app that reads Samsung Health steps count data by yourself! If you are having trouble, you may download this file:

Health Data Steps Complete Code
(573.4 KB)

To learn more about Samsung Health, visit:
developer.samsung.com/health