Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [0.0.16](https://github.com/vitoksmile/HealthKMP/releases/tag/0.0.16)

- Added **cycling pedaling cadence** data type
- Added **power** data type

## [0.0.15](https://github.com/vitoksmile/HealthKMP/releases/tag/0.0.15)

Breaking change:
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,33 @@ HealthKMP supports:
Note that for Android, the target device **needs** to have [Google Fit](https://www.google.com/fit/) or [Health Connect](https://health.google/health-connect-android/) installed.

## Supported data types (iOS, watchOS, Android)

- Blood glucose
- Blood pressure
- Body fat
- Body temperature
- Cycling pedaling cadence
- Exercise (segments, laps, routes)
- Heart rate
- Height
- Lean body mass
- Power
- Sleep
- Steps
- Weight

## Supported units

- Blood glucose: mmol/L, mg/dL
- Length: meters, kilometers, miles, inches, feet
- Mass: grams, kilograms, milligrams, micrograms, ounces, pounds
- Power: watts, kilocalories per day
- Percentage: e.g. 100%, 89.62%
- Pressure: millimeters of Mercury (mmHg)
- Temperature: Celsius, Fahrenheit

## Supported regional preferences

- Temperature: Celsius, Fahrenheit

## Requesting permission
Expand All @@ -65,6 +71,7 @@ SwiftUI is used for watchOS app.
First add the dependency to your project:

settings.gradle.kts:

```kotlin
dependencyResolutionManagement {
repositories {
Expand All @@ -74,20 +81,22 @@ dependencyResolutionManagement {
```

build.gradle:

```kotlin
sourceSets {
val commonMain by getting {
dependencies {
implementation("com.viktormykhailiv:health-kmp:0.0.15")
implementation("com.viktormykhailiv:health-kmp:0.0.16")
}
}
}
```

or use version catalog:

```
[versions]
health = "0.0.15"
health = "0.0.16"

[libraries]
health = { module = "com.viktormykhailiv:health-kmp", version.ref = "health" }
Expand All @@ -96,6 +105,7 @@ health = { module = "com.viktormykhailiv:health-kmp", version.ref = "health" }
```

build.gradle:

```
implementation(libs.health)
```
Expand Down Expand Up @@ -403,7 +413,7 @@ health.writeData(
recorded, unique identifier of data, and device information associated with the data.

```kotlin
fun generateMetadata() : Metadata {
fun generateMetadata(): Metadata {
return Metadata.manualEntry(
id = Uuid.random().toString(),
device = Device.getLocalDevice(),
Expand All @@ -418,7 +428,7 @@ HealthKMP is Swift compatible and can be added as package dependency to Xcode pr
### Add the package dependency

1. In Xcode, choose **File | Add Package Dependencies**.

2. In the search field, enter `https://github.com/vitoksmile/HealthKMP-SPM`:

<img src=readme/swift-package-manager.png width=640 />
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ RELEASE_SIGNING_ENABLED=true

GROUP=com.viktormykhailiv
POM_ARTIFACT_ID=health-kmp
VERSION_NAME=0.0.15
VERSION_NAME=0.0.16

POM_NAME=HealthKMP
POM_DESCRIPTION=Wrapper for HealthKit on iOS and Google Fit and Health Connect on Android
Expand Down
150 changes: 150 additions & 0 deletions health/api/health.api

Large diffs are not rendered by default.

187 changes: 187 additions & 0 deletions health/api/health.klib.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,48 @@ package com.viktormykhailiv.kmp.health
import androidx.health.connect.client.aggregate.AggregateMetric
import androidx.health.connect.client.aggregate.AggregationResult
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.PowerRecord
import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.WeightRecord
import com.viktormykhailiv.kmp.health.HealthDataType.BloodGlucose
import com.viktormykhailiv.kmp.health.HealthDataType.BloodPressure
import com.viktormykhailiv.kmp.health.HealthDataType.BodyFat
import com.viktormykhailiv.kmp.health.HealthDataType.BodyTemperature
import com.viktormykhailiv.kmp.health.HealthDataType.CyclingPedalingCadence
import com.viktormykhailiv.kmp.health.HealthDataType.Exercise
import com.viktormykhailiv.kmp.health.HealthDataType.HeartRate
import com.viktormykhailiv.kmp.health.HealthDataType.Height
import com.viktormykhailiv.kmp.health.HealthDataType.LeanBodyMass
import com.viktormykhailiv.kmp.health.HealthDataType.Power
import com.viktormykhailiv.kmp.health.HealthDataType.Sleep
import com.viktormykhailiv.kmp.health.HealthDataType.Steps
import com.viktormykhailiv.kmp.health.HealthDataType.Weight
import com.viktormykhailiv.kmp.health.aggregate.BloodGlucoseAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.BloodPressureAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.BodyFatAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.BodyTemperatureAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.CyclingPedalingCadenceAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.HeartRateAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.HeightAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.LeanBodyMassAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.PowerAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.SleepAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.StepsAggregatedRecord
import com.viktormykhailiv.kmp.health.aggregate.WeightAggregatedRecord
import com.viktormykhailiv.kmp.health.units.BloodGlucose as BloodGlucoseUnit
import com.viktormykhailiv.kmp.health.units.Mass
import com.viktormykhailiv.kmp.health.units.Temperature
import com.viktormykhailiv.kmp.health.units.BloodGlucose as BloodGlucoseUnit
import com.viktormykhailiv.kmp.health.units.kilograms
import com.viktormykhailiv.kmp.health.units.meters
import com.viktormykhailiv.kmp.health.units.millimetersOfMercury
import com.viktormykhailiv.kmp.health.units.percent
import kotlin.time.Instant
import com.viktormykhailiv.kmp.health.units.watts
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Instant
import kotlin.time.toKotlinDuration

/**
Expand Down Expand Up @@ -65,6 +72,13 @@ internal fun HealthDataType.toAggregateMetrics(): Set<AggregateMetric<Any>> = wh
BodyTemperature ->
throw IllegalArgumentException("Aggregated BodyTemperature is not supported and must be aggregated manually")

CyclingPedalingCadence ->
setOf(
CyclingPedalingCadenceRecord.RPM_AVG,
CyclingPedalingCadenceRecord.RPM_MIN,
CyclingPedalingCadenceRecord.RPM_MAX
)

is Exercise ->
throw IllegalArgumentException("Aggregated Exercise is not supported and must be aggregated manually")

Expand All @@ -77,6 +91,9 @@ internal fun HealthDataType.toAggregateMetrics(): Set<AggregateMetric<Any>> = wh
LeanBodyMass ->
throw IllegalArgumentException("Aggregated LeanBodyMass is not supported and must be aggregated manually")

Power ->
setOf(PowerRecord.POWER_AVG, PowerRecord.POWER_MIN, PowerRecord.POWER_MAX)

Sleep ->
setOf(SleepSessionRecord.SLEEP_DURATION_TOTAL)

Expand Down Expand Up @@ -129,6 +146,16 @@ internal fun AggregationResult.toHealthAggregatedRecord(
is BodyTemperature ->
throw IllegalArgumentException("Aggregated BodyTemperature is not supported and must be aggregated manually")

is CyclingPedalingCadence -> {
CyclingPedalingCadenceAggregatedRecord(
startTime = startTime,
endTime = endTime,
avg = get(CyclingPedalingCadenceRecord.RPM_AVG) ?: 0.0,
min = get(CyclingPedalingCadenceRecord.RPM_MIN) ?: 0.0,
max = get(CyclingPedalingCadenceRecord.RPM_MAX) ?: 0.0,
)
}

is Exercise ->
throw IllegalArgumentException("Aggregated Exercise is not supported and must be aggregated manually")

Expand All @@ -155,6 +182,16 @@ internal fun AggregationResult.toHealthAggregatedRecord(
is LeanBodyMass ->
throw IllegalArgumentException("Aggregated LeanBodyMass is not supported and must be aggregated manually")

is Power -> {
PowerAggregatedRecord(
startTime = startTime,
endTime = endTime,
avg = get(PowerRecord.POWER_AVG)?.toPower() ?: 0.watts,
min = get(PowerRecord.POWER_MIN)?.toPower() ?: 0.watts,
max = get(PowerRecord.POWER_MAX)?.toPower() ?: 0.watts,
)
}

is Sleep -> {
SleepAggregatedRecord(
startTime = startTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import androidx.health.connect.client.records.BloodGlucoseRecord
import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyFatRecord
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.LeanBodyMassRecord
import androidx.health.connect.client.records.PowerRecord
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.StepsRecord
Expand All @@ -21,6 +23,8 @@ import com.viktormykhailiv.kmp.health.HealthDataType.Exercise
import com.viktormykhailiv.kmp.health.HealthDataType.HeartRate
import com.viktormykhailiv.kmp.health.HealthDataType.Height
import com.viktormykhailiv.kmp.health.HealthDataType.LeanBodyMass
import com.viktormykhailiv.kmp.health.HealthDataType.CyclingPedalingCadence
import com.viktormykhailiv.kmp.health.HealthDataType.Power
import com.viktormykhailiv.kmp.health.HealthDataType.Sleep
import com.viktormykhailiv.kmp.health.HealthDataType.Steps
import com.viktormykhailiv.kmp.health.HealthDataType.Weight
Expand All @@ -35,6 +39,8 @@ internal fun HealthDataType.toRecordType(): KClass<out Record> = when (this) {

BodyTemperature -> BodyTemperatureRecord::class

CyclingPedalingCadence -> CyclingPedalingCadenceRecord::class

is Exercise -> ExerciseSessionRecord::class

HeartRate -> HeartRateRecord::class
Expand All @@ -43,6 +49,8 @@ internal fun HealthDataType.toRecordType(): KClass<out Record> = when (this) {

LeanBodyMass -> LeanBodyMassRecord::class

Power -> PowerRecord::class

Sleep -> SleepSessionRecord::class

Steps -> StepsRecord::class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.viktormykhailiv.kmp.health.HealthDataType.Exercise
import com.viktormykhailiv.kmp.health.HealthDataType.HeartRate
import com.viktormykhailiv.kmp.health.HealthDataType.Height
import com.viktormykhailiv.kmp.health.HealthDataType.LeanBodyMass
import com.viktormykhailiv.kmp.health.HealthDataType.CyclingPedalingCadence
import com.viktormykhailiv.kmp.health.HealthDataType.Power
import com.viktormykhailiv.kmp.health.HealthDataType.Sleep
import com.viktormykhailiv.kmp.health.HealthDataType.Steps
import com.viktormykhailiv.kmp.health.HealthDataType.Weight
Expand Down Expand Up @@ -44,6 +46,8 @@ internal fun HealthDataType.toDataType(): DataType = when (this) {

BodyTemperature -> throw IllegalArgumentException("BodyTemperature is not supported")

CyclingPedalingCadence -> throw IllegalArgumentException("PedalingCadence is not supported")

is Exercise -> throw IllegalArgumentException("Exercise is not supported")

HeartRate -> DataType.TYPE_HEART_RATE_BPM
Expand All @@ -52,6 +56,8 @@ internal fun HealthDataType.toDataType(): DataType = when (this) {

LeanBodyMass -> throw IllegalArgumentException("LeanBodyMass is not supported")

Power -> throw IllegalArgumentException("Power is not supported")

Sleep -> DataType.TYPE_SLEEP_SEGMENT

Steps -> DataType.TYPE_STEP_COUNT_DELTA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.content.Context
import com.google.android.gms.fitness.data.DataPoint
import com.google.android.gms.fitness.data.DataSet
import com.google.android.gms.fitness.data.DataSource
import com.google.android.gms.fitness.data.Device as FitnessDevice
import com.google.android.gms.fitness.data.Field
import com.google.android.gms.fitness.data.SleepStages
import com.viktormykhailiv.kmp.health.HealthDataType
Expand All @@ -16,6 +15,8 @@ import com.viktormykhailiv.kmp.health.HealthDataType.Exercise
import com.viktormykhailiv.kmp.health.HealthDataType.HeartRate
import com.viktormykhailiv.kmp.health.HealthDataType.Height
import com.viktormykhailiv.kmp.health.HealthDataType.LeanBodyMass
import com.viktormykhailiv.kmp.health.HealthDataType.CyclingPedalingCadence
import com.viktormykhailiv.kmp.health.HealthDataType.Power
import com.viktormykhailiv.kmp.health.HealthDataType.Sleep
import com.viktormykhailiv.kmp.health.HealthDataType.Steps
import com.viktormykhailiv.kmp.health.HealthDataType.Weight
Expand All @@ -35,8 +36,9 @@ import com.viktormykhailiv.kmp.health.records.metadata.getLocalDevice
import com.viktormykhailiv.kmp.health.units.Length
import com.viktormykhailiv.kmp.health.units.Mass
import com.viktormykhailiv.kmp.health.units.percent
import kotlin.time.Instant
import java.util.concurrent.TimeUnit
import kotlin.time.Instant
import com.google.android.gms.fitness.data.Device as FitnessDevice

internal fun List<DataPoint>.toHealthRecords(type: HealthDataType): List<HealthRecord> {
return when (type) {
Expand All @@ -59,6 +61,9 @@ internal fun List<DataPoint>.toHealthRecords(type: HealthDataType): List<HealthR
is BodyTemperature ->
throw IllegalArgumentException("BodyTemperature is not supported")

is CyclingPedalingCadence ->
throw IllegalArgumentException("PedalingCadence is not supported")

is Exercise ->
throw IllegalArgumentException("Exercise is not supported")

Expand Down Expand Up @@ -93,6 +98,9 @@ internal fun List<DataPoint>.toHealthRecords(type: HealthDataType): List<HealthR
is LeanBodyMass ->
throw IllegalArgumentException("LeanBodyMass is not supported")

is Power ->
throw IllegalArgumentException("Power is not supported")

is Sleep -> {
val metadata = firstOrNull().toMetadata()
map { dataPoint ->
Expand Down
Loading