Migrating Exercise app to Samsung Health Data SDK

As of July 2025, Samsung Health SDK for Android has been deprecated. While existing functionality will continue to operate for now, developers are strongly encouraged to migrate to Samsung Health Data SDK to ensure long-term compatibility and continued support.

Both SDKs provide access to health data, stored in Samsung Health app. This data may originate directly from the app itself, as is the case for steps, or from connected wearable devices such as the ring, watch or dedicated medical equipment. Samsung Health Data SDK offers several advantages over its predecessor:

  • More user-friendly API
  • Support for more features such as suspend functions
  • Cleaner and more compact source code

For more information and resources, refer to Samsung Health Data SDK introduction.

The steps on this page help you easily understand the overall process of migrating your app that reads and inserts exercise data to use the Samsung Health Data SDK.

Prerequisites

Set up your environment

Before you begin the integration, make sure that you have the following installed and ready:

  • Android Studio
  • Java JDK 17 or higher
  • Android mobile device compatible with the latest version of Samsung Health

Set up your Android device

Refer to the following resources to set up your Android device:

Download Samsung Health Data SDK

Download Samsung Health Data SDK from the official developer website.

Partnership registration

When your app is ready for public release, you must become a Samsung Health partner and register the app in Samsung Health’s systems by providing the required information, including the app package name and signature (SHA-256). You can submit a request for partnership here.

However, you can enable developer mode for testing with an app whose signature is not registered in Samsung Health’s systems. An access code is additionally required to test inserting exercise data into Samsung Health. The access code is provided after partnership approval.

Project setup

Import SDK library

Replace Samsung Health SDK for Android AAR file in your project:

  1. Navigate to app/libs in your project.
  2. Replace samsung-health-data-1.5.1.aar with samsung-health-data-api-1.0.0.aar from the downloaded SDK package.

App manifest

When using Samsung Health Data SDK, your app’s manifest should no longer include any entries related to health data permissions.

Gradle settings

SDK dependency

Update your module-level build.gradle to include the correct dependency.

Replace:

dependencies {
    implementation(files("/libs/samsung-health-data-1.5.1.aar"))
}

with entry (Samsung Health Data SDK version independent):

dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
}

Other configuration

  • Add gson library to the dependencies of module-level build.gradle:
dependencies {
    implementation("com.google.code.gson:gson:2.13.2")
}
  • Apply the kotlin-parcelize plugin to the module-level build.gradle:
plugins {
    id("kotlin-parcelize")
}

Additionally, check the module-level build.gradle file and include any other dependencies required by your implementation.

Connect with Samsung Health

After completing the initial setup, you are ready to connect your app to Samsung Health to access exercise data.

In the following paragraphs, we are going to show you how to migrate specific elements of logic from Samsung Health SDK for Android to Samsung Health Data SDK.

Samsung Health SDK for Android

With Samsung Health SDK for Android, establishing a connection requires the following:

  1. Import HealthDataStore:
import com.samsung.android.sdk.healthdata.HealthDataStore
  1. Declare HealthDataStore instance with:
lateinit var healthDataStore: HealthDataStore
  1. Initialize the instance and start the connection with:
healthDataStore = HealthDataStore(context, listener).also { it.connectService() }

These are the provided arguments:

  • listener – HealthDataStore.ConnectionListener instance, used to observe the connection results and handle connection failures
  • context – typically app or activity context
     

Samsung Health Data SDK

With Samsung Health Data SDK, establishing a connection is simpler, as there is no need to implement a connection listener.

  1. Import the following class:
import com.samsung.android.sdk.health.data.HealthDataStore
  1. We can now establish the connection and obtain a HealthDataStore instance by calling:
HealthDataService.getStore(context)

Here, context can be your app context.

Request permissions

Upon successful connection to Samsung Health, your app must ensure that it has permissions to access the health data from Samsung Health. Since Samsung SDKs enforce data access control, defining and checking permissions must be done before any data operations are performed.

Samsung Health SDK for Android

In Samsung Health SDK for Android, you must first create a permission set, specifying which data type your app intends to access and the access permission (read or write). For example, to request read and write permissions for Exercise data:

val PERMISSIONS = setOf(
    HealthPermissionManager.PermissionKey(
        HealthConstants.Exercise.HEALTH_DATA_TYPE,
        HealthPermissionManager.PermissionType.READ
    ),
    HealthPermissionManager.PermissionKey(
        HealthConstants.Exercise.HEALTH_DATA_TYPE,
        HealthPermissionManager.PermissionType.WRITE
    )
)

Then, to check existing permissions and request any that are missing, use the HealthPermissionManager class:

val permissionManager = HealthPermissionManager(healthDataStore)
runCatching {
    val grantedPermissions = permissionManager.isPermissionAcquired(PERMISSIONS)
    if (grantedPermissions.values.all { it }) {
        Log.i(APP_TAG, "All required permissions granted")
    } else {
        Log.i(APP_TAG, "Not all required permissions granted")
        permissionManager.requestPermissions(PERMISSIONS, context)
            .setResultListener(permissionListener)
    }
}.onFailure { error ->
    error.message?.let { Log.i(APP_TAG, it) }
}

Samsung Health Data SDK

In Samsung Health Data SDK, requesting data access permissions is simpler.

For example, to request read and write permissions for Exercise data:

val PERMISSIONS = setOf(
    Permission.of(DataTypes.EXERCISE, AccessType.READ),
    Permission.of(DataTypes.EXERCISE, AccessType.WRITE)
)

Then, to check if all necessary permissions have been granted:

val grantedPermissions = 
    healthDataStore.getGrantedPermissions(PERMISSIONS)
val areAllPermissionsGranted = 
    grantedPermissions.containsAll(PERMISSIONS)

If there are any missing permissions, prompt the user to grant them:

try {
    val result = healthDataStore.requestPermissions(PERMISSIONS, context)
    // …
} // catch any error

Read exercise data

Samsung Health SDK for Android

In Samsung Health SDK for Android, to read exercise data, you need to create a read request by defining the data type and time range.

Set the data type using HealthConstants.Exercise.HEALTH_DATA_TYPE and specify the local time range using setLocalTimeRange, which accepts the start and end time in Longs:

val readRequest = HealthDataResolver.ReadRequest.Builder()
    .setDataType(HealthConstants.Exercise.HEALTH_DATA_TYPE)
    .setLocalTimeRange(
        HealthConstants.Exercise.START_TIME,
        HealthConstants.Exercise.TIME_OFFSET,
        startTime,
        endTime
    )
    .build()

Next, initialize a HealthDataResolver object using the previously obtained healthDataStore instance (and handler) and launch the prepared readRequest to process the results. For example, you can retrieve the device ID associated with each recorded Exercise. This is useful when you want to determine which device the data originates from.

To achieve this, you can create an iterator to access healthData that represents an Exercise record.

val healthDataResolver = HealthDataResolver(healthDataStore, handler)
try {
    healthDataResolver.read(readRequest).await().run {
        try {
            val iterator = iterator()
            while (iterator.hasNext()) {
                val healthData = iterator.next()
                val deviceId = 
                    healthData.getString(HealthConstants.Exercise.DEVICE_UUID)
                // process obtained deviceId or other data
            }
        } finally {
            close()
        }
    }
} catch (exception: Exception) {
    exception.message?.let { Log.i(TAG, it) }
}

Samsung Health Data SDK

In Samsung Health Data SDK, you can achieve the same result with simpler and more concise code. As before, you need to create a read request.

First, create a LocalTimeFilter to access data for the current day:

val startTime = LocalDate.now().atStartOfDay()
val endTime = LocalDateTime.now()
val localTimeFilter = LocalTimeFilter.of(startTime, endTime)

Then, apply it to the prepared request:

val readRequest = DataTypes.EXERCISE.readDataRequestBuilder
    .setLocalTimeFilter(localTimeFilter)
    .build()

To launch the request, simply call the readData() method of the HealthDataStore instance, passing readRequest as an argument. The result includes a dataList, which you can iterate through to access exercise data:

val exerciseDataList = healthDataStore.readData(readRequest).dataList
exerciseDataList.forEach { healthDataPoint ->
    val deviceId = healthDataPoint.dataSource?.deviceId
    // process obtained deviceId or other data
}

Read aggregated data

For data types that represent summarized values, such as total, minimum, maximum or last values, you can use aggregate requests to compute these results.

Samsung Health SDK for Android

In Samsung Health SDK for Android, to read aggregated data for a specific period, you need to create AggregateRequest with a defined time range and aggregation function.

For example, to read the total calories burned from exercise since the start of the day, you can call:

  • addFunction() with AggregateFunction.SUM
  • setLocalTimeRange() with startTime as the start of the day and endTime as the current time
  • setDataType() with HealthConstants.Exercise.HEALTH_DATA_TYPE

Below is an implementation of AggregateRequest that reads the total exercise calories since the start of the day.

Note that you also need to define the key ID to retrieve the aggregated data later.

val exerciseTotalCaloriesId = "exercise_total_calories"
val aggregateRequest = HealthDataResolver.AggregateRequest.Builder()
    .addFunction(
        HealthDataResolver.AggregateRequest.AggregateFunction.SUM,
        HealthConstants.Exercise.CALORIE,
        exerciseTotalCaloriesId
    )
    .setLocalTimeRange(
        HealthConstants.Exercise.START_TIME,
        HealthConstants.Exercise.TIME_OFFSET,
        startTime, endTime
    )
    .setDataType(HealthConstants.Exercise.HEALTH_DATA_TYPE)
    .build()

Next, launch the request synchronously and iterate through the results to get the total calories.

var totalCalories = 0
try {
    healthDataResolver.aggregate(aggregateRequest).await().run {
        try {
            val iterator = iterator()
            if (iterator.hasNext()) {
                val healthData = iterator.next()
                totalCalories = healthData.getInt(exerciseTotalCaloriesId)
            }
        } finally {
            close()
        }
    }
} catch (exception: Exception) {
    exception.message?.let { Log.i(TAG, it) }
}

To get the total exercise duration, the logic remains the same. The only difference is the property argument of the request’s addFunction(), as well as the key to retrieve the aggregated data:

.addFunction(
    HealthDataResolver.AggregateRequest.AggregateFunction.SUM,
    HealthConstants.Exercise.DURATION,
    "exercise_total_duration"
)

Samsung Health Data SDK

In Samsung Health Data SDK, it is simpler to get the aggregated data. First, you need to create LocalTimeFilter to define the time range:

val startTime = LocalDate.now().atStartOfDay()
val endTime = LocalDateTime.now()
val localTimeFilter = LocalTimeFilter.of(startTime, endTime)

Then, build an AggregateRequest instance by calling requestBuilder on the desired aggregate data type. For exercise data, this could be either DataType.ExerciseType.TOTAL_CALORIES or DataType.ExerciseType.TOTAL_DURATION.

The full request is shown below:

val aggregateRequest = DataType.ExerciseType.TOTAL_CALORIES.requestBuilder
    .setLocalTimeFilter(localTimeFilter)
    .build()

To obtain the aggregated data, create an aggregate request and access the datalist. Since this type of request returns a single-element list containing the aggregated value, you can use Kotlin’s mapNotNull() function to extract it safely, ensuring it’s not null:

healthDataStore.aggregateData(aggregateRequest).dataList.mapNotNull { it.value }

Insert data

Let's explore how to insert prepared exercise data into Samsung Health.

Samsung Health SDK for Android

To insert data in Samsung Health SDK for Android, you first need to create a HealthData instance that contains the exercise data to be inserted. The health data includes the start time, end time, exercise type or duration, and live data (time-series data across different exercise stages). The LiveData placeholder contains the user’s heart rate or speed across each exercise phase.

Let's prepare a set of dummy data to insert. In real-world scenarios, HealthData includes real-time measurements captured from wearable devices or other health and fitness equipment.

Prepare data to be inserted

  1. First, create LiveData - a list of objects of your custom data class ExerciseLiveData.

According to the documentation for Samsung Health SDK for Android, it is recommended to provide start_time and at least one additional field for each instance of LiveData. For more details, please refer to LiveData.

Declare the start_time, heart_rate and speed data:

val liveDataList = listOf(
    ExerciseLiveData(
        start_time = Instant.ofEpochSecond(1766394000).toEpochMilli(),
        heart_rate = 144f,
        speed = 1.6f
    ),
    ExerciseLiveData(
        start_time = Instant.ofEpochSecond(1766394030).toEpochMilli(),
        heart_rate = 146f,
        speed = 1.8f
    ),
    // add more entries
)
  1. Convert LiveData list.

Next, convert the LiveData list to JsonBlob (ByteArray) format by using the SDK function getJsonBlob(). This function takes a list of ExerciseLiveData as an argument and returns ByteArray. You can simply create a helper function for this task:

private fun createLiveData(liveDataList: List<ExerciseLiveData>): ByteArray {
    val zip = HealthDataUtil.getJsonBlob(liveDataList)
    return zip
}
  1. Create HealthData object.

Create a HealthData object that holds all exercise data, including timestamps, calories, distance, duration and LiveData. The LiveData is attached using the putBlob() function. In addition to the standard fields start_time, end_time, and time_offset, it is necessary to specify the Exercise type. Based on the Predefined Exercise Type table, setting the value to 1002 represents a running exercise:

val calories = 73f
val distance = 1000f
val exerciseType = 1002
val startTime = Instant.ofEpochSecond(1766394000)
val endTime = Instant.ofEpochSecond(1766394300)
val duration = Duration.between(startTime, endTime)
val timeOffset = TimeZone.getDefault().getOffset(endTime.toEpochMilli()).toLong()

val healthData = HealthData().apply {
    sourceDevice = deviceId
    putLong(HealthConstants.Exercise.START_TIME, startTime.toEpochMilli())
    putLong(HealthConstants.Exercise.END_TIME, endTime.toEpochMilli())
    putLong(HealthConstants.Exercise.TIME_OFFSET, timeOffset)
    putInt(HealthConstants.Exercise.EXERCISE_TYPE, exerciseType)
    putLong(HealthConstants.Exercise.DURATION, duration.toMillis())
    putFloat(HealthConstants.Exercise.CALORIE, calories)
    putFloat(HealthConstants.Exercise.DISTANCE, distance)
    putBlob(HealthConstants.Exercise.LIVE_DATA, createLiveData(liveDataList))
}

Insert the prepared data

Use a HealthDataResolver class to build an insertRequest. Initialize a HealthDataResolver instance and pass in the already connected healthDataStore.

Utilize a Builder() function and set HealthConstants.Exercise.HEALTH_DATA_TYPE as the data type.

Retrieve the local device ID using the HealthDeviceManager instance, as you need to specify the source device that provides this data.

Call the insert() function with the prepared insertRequest and wait for the result.

Below is an implementation example:

val handler = Handler(Looper.getMainLooper())
val healthDataResolver = HealthDataResolver(healthDataStore, handler)
try {
    val localDevice = HealthDeviceManager(healthDataStore).localDevice.uuid
    val data = ExerciseInsertData.getExerciseData(endTime, localDevice)
    if (data != null) {
        val insertRequest = HealthDataResolver.InsertRequest.Builder()
            .setDataType(HealthConstants.Exercise.HEALTH_DATA_TYPE)
            .build()
        insertRequest.addHealthData(data)

        val result = healthDataResolver.insert(insertRequest).await()
        if (result.status == HealthResultHolder.BaseResult.STATUS_SUCCESSFUL) {
            Log.i(TAG, "Inserted running exercise. Count of data: ${result.count}")
        } else {
            Log.i(TAG, "Inserting failed")
        }
    }
} catch (e: Exception) {
    throw e
}

Samsung Health Data SDK

In Samsung Health Data SDK, preparing LiveData to be inserted into Samsung Health app is simplified by the help of the ExerciseLog class. With predefined fields such as timestamp, heartrate, cadence and more, this class is ready to capture information for each phase of an exercise session. This eliminates the need to manually encode it to JsonBlob (ByteArray) format. Instead, you can simply create a list of ExerciseLog objects, each representing a phase of the exercise.

val exerciseLog = listOf(
    ExerciseLog.of(
        timestamp = Instant.ofEpochSecond(1766394000),
        heartRate = 144f,
        speed = 1.6f,
        cadence = null,
        count = null,
        power = null
    ),
    ExerciseLog.of(
        timestamp = Instant.ofEpochSecond(1766394030),
        heartRate = 146f,
        speed = 1.8f,
        cadence = null,
        count = null,
        power = null
    )
    // add more entries
)

Create a HealthDataPoint instance

Another key difference in Samsung Health Data SDK is that, instead of creating a HealthData instance, the SDK provides a HealthDataPoint object that represents the entire exercise.

Before creating a HealthDataPoint instance, you need the following:

  • List of ExerciseLog objects.

  • Predefined exercise type.

    Use addFieldData() function to set the exercise type property with a predefined enum value and include list of DataType.ExerciseType.SESSIONS.

  • ExerciseSession object.

    In the Running exercise, there will always be a single-element list - an ExerciseSession element created based on a previously defined list of ExerciseLog objects. To create an ExerciseSession object, use ExerciseSession.builder() and set the relevant data, such as start time, end time, exercise type, calories and duration.

Once the relevant information is prepared, you can call the builder() method to initialize a HealthDataPoint instance and set the properties, such as start time, end time, and exercise type, by calling the addFieldData() function. The code is shown below:

val calories = 73f
val distance = 1000f
val startTime = Instant.ofEpochSecond(1766394000)
val endTime = Instant.ofEpochSecond(1766394300)
val duration = Duration.between(startTime, endTime)

var healthDataPoint: HealthDataPoint?
try {
    val session = ExerciseSession.builder()
        .setStartTime(startTime)
        .setEndTime(endTime)
        .setExerciseType(DataType.ExerciseType.PredefinedExerciseType.RUNNING)
        .setDuration(duration)
        .setCalories(calories)
        .setDistance(distance)
        .setComment("Routine running")
        .setLog(exerciseLog)
        .build()

    healthDataPoint = HealthDataPoint.builder()
        .setStartTime(startTime)
        .setEndTime(endTime)
        .addFieldData(
            DataType.ExerciseType.EXERCISE_TYPE,
            DataType.ExerciseType.PredefinedExerciseType.RUNNING
        )
        .addFieldData(DataType.ExerciseType.SESSIONS, listOf(session))
        .build()
} catch (e: Exception) {
    throw e
}

Insert the prepared data

  1. To insert the prepared data, simply build insertRequest by calling the insertDataRequestBuilder method on DataTypes.EXERCISE. Attach the created data (HealthDataPoint instance) by calling the addData() function. Finalize the request with the build() method.

  1. Next, call the insertData() method of the HealthDataStore instance and pass the created insertRequest as an argument:

    val insertRequest = DataTypes.EXERCISE.insertDataRequestBuilder
       .addData(data)
       .build()
    healthDataStore.insertData(insertRequest)
    

Exception handling

Robust health apps can recover when things go wrong. Let's examine the differences between both SDKs when handling such scenarios.

Samsung Health SDK for Android

In the case of Samsung Health SDK for Android, the methods for handling errors depend on the exception types.

For example, connection exceptions with Samsung Health are handled by the HealthDataStore.ConnectionListener's event handler. You can use the HealthConnectionError class to check whether the error has a resolution. Based on the error type, you can implement different handling strategies.

For exceptions related to requests such as reading or writing data, refer to the HealthResultHolder.BaseResult class and its getStatus() method. However, since there are no dedicated exception class, developers must handle the exceptions that are part of the standard Java language, such as IllegalArgumentException, SecurityException or IllegalStateException.

Samsung Health Data SDK

When working with Samsung Health Data SDK, key functions such as aggregateData(), requestPermissions(), or getGrantedPermissions() may throw exceptions under certain conditions.

To prevent unexpected crashes or blank screens, it’s best to funnel these exceptions into a central error handler.

You can do so by wrapping every API invocation with:

try {
    // SDK function invocation
} catch (e: HealthDataException) {
    handleHealthDataException(e)
} 

About HealthDataException

HealthDataException is the main exception class for Samsung Health Data SDK.

The handleHealthDataException() function:

• Shows an error log.
• Tries to “resolve” the exception

Some HealthDataException instances include a resolution intent. You can check if the exception has a resolution by checking the hasResolution property of the exception object and trying to fix it by invoking resolve().

Examples of such exceptions:

ERR_OLD_VERSION_PLATFORM: Indicates Samsung Health app is outdated.
ERR_PLATFORM_NOT_INSTALLED: Signals that Samsung Health app is not installed.

In both cases, the resolution involves opening the device’s app marketplace to prompt the user to install or update Samsung Health app. Once the issue is fixed, control returns to the app so it can continue functioning.

This approach not only keeps your UI clean, but allows for a consistent, guided error experience. When something goes wrong, users can see a clear message of what happened and how to fix it.

Summary

We have covered how to migrate an app that uses the Samsung Health SDK for Android to the Samsung Health Data SDK.

When migrating your app to the Samsung Health Data SDK, you can follow the described process and code examples to complete the migration.

For more details, please refer to Samsung Health Data SDK introduction.