Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,34 @@ class HealthDataConverter {
* @return List<Map<String, Any?>> List of converted records (some records may split into multiple entries)
* @throws IllegalArgumentException If the record type is not supported
*/
fun convertRecord(record: Any, dataType: String): List<Map<String, Any?>> {
fun convertRecord(record: Any, dataType: String, dataUnit: String? = null): List<Map<String, Any?>> {
val metadata = (record as Record).metadata

return when (record) {
// Single-value instant records
is WeightRecord -> listOf(createInstantRecord(metadata, record.time, record.weight.inKilograms))
is HeightRecord -> listOf(createInstantRecord(metadata, record.time, record.height.inMeters))
is WeightRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) {
"POUND" -> record.weight.inPounds
else -> record.weight.inKilograms
}))
is HeightRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) {
"CENTIMETER" -> (record.height.inMeters * 100)
"INCH" -> record.height.inInches
else -> record.height.inMeters
}))
is BodyFatRecord -> listOf(createInstantRecord(metadata, record.time, record.percentage.value))
is LeanBodyMassRecord -> listOf(createInstantRecord(metadata, record.time, record.mass.inKilograms))
is HeartRateVariabilityRmssdRecord -> listOf(createInstantRecord(metadata, record.time, record.heartRateVariabilityMillis))
is BodyTemperatureRecord -> listOf(createInstantRecord(metadata, record.time, record.temperature.inCelsius))
is BodyTemperatureRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) {
"DEGREE_FAHRENHEIT" -> record.temperature.inFahrenheit
"KELVIN" -> record.temperature.inCelsius + 273.15
else -> record.temperature.inCelsius
}))
is BodyWaterMassRecord -> listOf(createInstantRecord(metadata, record.time, record.mass.inKilograms))
is OxygenSaturationRecord -> listOf(createInstantRecord(metadata, record.time, record.percentage.value))
is BloodGlucoseRecord -> listOf(createInstantRecord(metadata, record.time, record.level.inMilligramsPerDeciliter))
is BloodGlucoseRecord -> listOf(createInstantRecord(metadata, record.time, when (dataUnit) {
"MILLIMOLES_PER_LITER" -> record.level.inMillimolesPerLiter
else -> record.level.inMilligramsPerDeciliter
}))
is BasalMetabolicRateRecord -> listOf(createInstantRecord(metadata, record.time, record.basalMetabolicRate.inKilocaloriesPerDay))
is RestingHeartRateRecord -> listOf(createInstantRecord(metadata, record.time, record.beatsPerMinute))
is RespiratoryRateRecord -> listOf(createInstantRecord(metadata, record.time, record.rate))
Expand Down Expand Up @@ -236,7 +250,7 @@ class HealthDataConverter {
)
)
}

companion object {
private const val BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC"
private const val MEAL_UNKNOWN = "UNKNOWN"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,45 @@ class HealthDataOperations(
}
}

/**
* Deletes a specific health record by its client record ID and data type. Allows precise
* deletion of individual health records using client-side IDs.
*
* @param call Method call containing 'dataTypeKey', 'recordId', and 'clientRecordId'
* @param result Flutter result callback returning boolean success status
*/
fun deleteByClientRecordId(call: MethodCall, result: Result) {
val arguments = call.arguments as? HashMap<*, *>
val dataTypeKey = (arguments?.get("dataTypeKey") as? String)!!
val recordId = listOfNotNull(arguments["recordId"] as? String)
val clientRecordId = listOfNotNull(arguments["clientRecordId"] as? String)
if (!HealthConstants.mapToType.containsKey(dataTypeKey)) {
Log.w("FLUTTER_HEALTH::ERROR", "Datatype $dataTypeKey not found in HC")
result.success(false)
return
}
val classType = HealthConstants.mapToType[dataTypeKey]!!

scope.launch {
try {
healthConnectClient.deleteRecords(
classType,
recordId,
clientRecordId
)
result.success(true)
} catch (e: Exception) {
Log.e(
"FLUTTER_HEALTH::ERROR",
"Error deleting record with ClientRecordId: $clientRecordId"
)
Log.e("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error")
Log.e("FLUTTER_HEALTH::ERROR", e.stackTraceToString())
result.success(false)
}
}
}

/**
* Internal helper method to prepare Health Connect permission strings. Converts data type names
* and access levels into proper permission format.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.os.Handler
import android.util.Log
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.*
import androidx.health.connect.client.request.AggregateGroupByDurationRequest
import androidx.health.connect.client.request.AggregateRequest
Expand Down Expand Up @@ -40,19 +41,27 @@ class HealthDataReader(
*/
fun getData(call: MethodCall, result: Result) {
val dataType = call.argument<String>("dataTypeKey")!!
val dataUnit: String? = call.argument<String>("dataUnitKey")
val startTime = Instant.ofEpochMilli(call.argument<Long>("startTime")!!)
val endTime = Instant.ofEpochMilli(call.argument<Long>("endTime")!!)
val healthConnectData = mutableListOf<Map<String, Any?>>()
val recordingMethodsToFilter = call.argument<List<Int>>("recordingMethodsToFilter")!!

Log.i(
"FLUTTER_HEALTH",
"Getting data for $dataType between $startTime and $endTime, filtering by $recordingMethodsToFilter"
"Getting data for $dataType with unit $dataUnit between $startTime and $endTime, filtering by $recordingMethodsToFilter"
)

scope.launch {
try {
HealthConstants.mapToType[dataType]?.let { classType ->
val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions()

val authorizedTypeMap = HealthConstants.mapToType.filter { (typeKey, classType) ->
val requiredPermission = HealthPermission.getReadPermission(classType)
grantedPermissions.contains(requiredPermission)
}

authorizedTypeMap[dataType]?.let { classType ->
val records = mutableListOf<Record>()

// Set up the initial request to read health records
Expand Down Expand Up @@ -92,7 +101,7 @@ class HealthDataReader(
)
for (rec in filteredRecords) {
healthConnectData.addAll(
dataConverter.convertRecord(rec, dataType)
dataConverter.convertRecord(rec, dataType, dataUnit)
)
}
}
Expand All @@ -105,7 +114,7 @@ class HealthDataReader(
"Unable to return $dataType due to the following exception:"
)
Log.e("FLUTTER_HEALTH::ERROR", Log.getStackTraceString(e))
result.success(null)
result.success(emptyList<Map<String, Any?>>()) // Return empty list instead of null
}
}
}
Expand Down
Loading