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
12 changes: 11 additions & 1 deletion packages/health/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
## 12.0.2
## 12.1.0

* iOS: Parse metadata to remove unsupported types - PR [#1120](https://github.com/cph-cachet/flutter-plugins/pull/1120)
* iOS: Add UV Index Types
* Android: Add request access to historic data [#1126](https://github.com/cph-cachet/flutter-plugins/issues/1126) - PR [#1127](https://github.com/cph-cachet/flutter-plugins/pull/1127)
```XML
<!-- Add the following permission into AndroidManifest.xml -->
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
```
* Android:
* Update `androidx.compose:compose-bom` to `2025.02.00`
* Update `androidx.health.connect:connect-client` to `1.1.0-alpha11`
* Update `androidx.fragment:fragment-ktx` to `1.8.6`
* Update to Java 11
* Update example apps

## 12.0.1
Expand Down
10 changes: 10 additions & 0 deletions packages/health/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ An example of asking for permission to read and write heart rate data is shown b
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
```

By default, Health Connect restricts read data to 30 days from when permission has been granted.

You can check and request access to historical data using the `isHealthDataHistoryAuthorized` and `requestHealthDataHistoryAuthorization` methods, respectively.

The above methods require the following permission to be declared:

```xml
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
```

Accessing fitness data (e.g. Steps) requires permission to access the "Activity Recognition" API. To set it add the following line to your `AndroidManifest.xml` file.

```xml
Expand Down
14 changes: 7 additions & 7 deletions packages/health/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 34
compileSdk 34

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
}

sourceSets {
Expand All @@ -51,12 +51,12 @@ android {
}

dependencies {
def composeBom = platform('androidx.compose:compose-bom:2022.10.00')
def composeBom = platform('androidx.compose:compose-bom:2025.02.00')
implementation(composeBom)
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

implementation("androidx.health.connect:connect-client:1.1.0-alpha07")
def fragment_version = "1.6.2"
implementation("androidx.health.connect:connect-client:1.1.0-alpha11")
def fragment_version = "1.8.6"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.NonNull
import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.HealthConnectFeatures
import androidx.health.connect.client.PermissionController
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_HISTORY
import androidx.health.connect.client.records.*
import androidx.health.connect.client.records.MealType.MEAL_TYPE_BREAKFAST
import androidx.health.connect.client.records.MealType.MEAL_TYPE_DINNER
Expand Down Expand Up @@ -147,6 +150,9 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
when (call.method) {
"installHealthConnect" -> installHealthConnect(call, result)
"getHealthConnectSdkStatus" -> getHealthConnectSdkStatus(call, result)
"isHealthDataHistoryAvailable" -> isHealthDataHistoryAvailable(call, result)
"isHealthDataHistoryAuthorized" -> isHealthDataHistoryAuthorized(call, result)
"requestHealthDataHistoryAuthorization" -> requestHealthDataHistoryAuthorization(call, result)
"hasPermissions" -> hasPermissions(call, result)
"requestAuthorization" -> requestAuthorization(call, result)
"revokePermissions" -> revokePermissions(call, result)
Expand Down Expand Up @@ -512,6 +518,55 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
}
}

/**
* Checks if the health data history feature is available on this device
*/
@OptIn(ExperimentalFeatureAvailabilityApi::class)
private fun isHealthDataHistoryAvailable(call: MethodCall, result: Result) {
scope.launch {
result.success(
healthConnectClient
.features
.getFeatureStatus(HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY) ==
HealthConnectFeatures.FEATURE_STATUS_AVAILABLE)
}
}

/**
* Checks if PERMISSION_READ_HEALTH_DATA_HISTORY has been granted
*/
private fun isHealthDataHistoryAuthorized(call: MethodCall, result: Result) {
scope.launch {
result.success(
healthConnectClient
.permissionController
.getGrantedPermissions()
.containsAll(listOf(PERMISSION_READ_HEALTH_DATA_HISTORY)),
)
}
}

/**
* Requests authorization for PERMISSION_READ_HEALTH_DATA_HISTORY
*/
private fun requestHealthDataHistoryAuthorization(call: MethodCall, result: Result) {
if (context == null) {
result.success(false)
return
}

if (healthConnectRequestPermissionsLauncher == null) {
result.success(false)
Log.i("FLUTTER_HEALTH", "Permission launcher not found")
return
}

// Store the result to be called in [onHealthConnectPermissionCallback]
mResult = result
isReplySubmitted = false
healthConnectRequestPermissionsLauncher!!.launch(setOf(PERMISSION_READ_HEALTH_DATA_HISTORY))
}

private fun hasPermissions(call: MethodCall, result: Result) {
val args = call.arguments as HashMap<*, *>
val types = (args["types"] as? ArrayList<*>)?.filterIsInstance<String>()!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
<uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS"/>
<uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS"/>

<!-- For reading historical data - more than 30 days ago since permission given -->
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>

<application
android:label="health_example"
android:name="${applicationName}"
Expand Down
22 changes: 13 additions & 9 deletions packages/health/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ class HealthAppState extends State<HealthApp> {
try {
authorized =
await health.requestAuthorization(types, permissions: permissions);

// request access to read historic data
await health.requestHealthDataHistoryAuthorization();

} catch (error) {
debugPrint("Exception in authorize: $error");
}
Expand Down Expand Up @@ -289,10 +293,10 @@ class HealthAppState extends State<HealthApp> {
startTime: earlier,
endTime: now);
success &= await health.writeHealthData(
value: 22,
type: HealthDataType.LEAN_BODY_MASS,
startTime: earlier,
endTime: now);
value: 22,
type: HealthDataType.LEAN_BODY_MASS,
startTime: earlier,
endTime: now);

// specialized write methods
success &= await health.writeBloodOxygen(
Expand Down Expand Up @@ -400,11 +404,11 @@ class HealthAppState extends State<HealthApp> {
endTime: now,
recordingMethod: RecordingMethod.manual);
success &= await health.writeHealthData(
value: 4.3,
type: HealthDataType.UV_INDEX,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
value: 4.3,
type: HealthDataType.UV_INDEX,
startTime: earlier,
endTime: now,
recordingMethod: RecordingMethod.manual);
}

setState(() {
Expand Down
2 changes: 1 addition & 1 deletion packages/health/ios/health.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'health'
s.version = '12.0.2'
s.version = '12.1.0'
s.summary = 'Wrapper for Apple\'s HealthKit on iOS and Google\'s Health Connect on Android.'
s.description = <<-DESC
Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android.
Expand Down
64 changes: 64 additions & 0 deletions packages/health/lib/src/health_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,70 @@ class Health {
}
}

/// Checks if the Health Data History feature is available.
///
/// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY()
///
///
/// Android only. Returns false on iOS or if an error occurs.
Future<bool> isHealthDataHistoryAvailable() async {
if (Platform.isIOS) return false;

try {
final status =
await _channel.invokeMethod<bool>('isHealthDataHistoryAvailable');
return status ?? false;
} catch (e) {
debugPrint(
'$runtimeType - Exception in isHealthDataHistoryAvailable(): $e');
return false;
}
}

/// Checks the current status of the Health Data History permission.
/// Make sure to check [isHealthConnectAvailable] before calling this method.
///
/// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY()
///
///
/// Android only. Returns true on iOS or false if an error occurs.
Future<bool> isHealthDataHistoryAuthorized() async {
if (Platform.isIOS) return true;

try {
final status =
await _channel.invokeMethod<bool>('isHealthDataHistoryAuthorized');
return status ?? false;
} catch (e) {
debugPrint(
'$runtimeType - Exception in isHealthDataHistoryAuthorized(): $e');
return false;
}
}

/// Requests the Health Data History permission.
///
/// Returns true if successful, false otherwise.
///
/// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY()
///
///
/// Android only. Returns true on iOS or false if an error occurs.
Future<bool> requestHealthDataHistoryAuthorization() async {
if (Platform.isIOS) return true;

await _checkIfHealthConnectAvailableOnAndroid();
try {
final bool? isAuthorized =
await _channel.invokeMethod('requestHealthDataHistoryAuthorization');
return isAuthorized ?? false;
} catch (e) {
debugPrint(
'$runtimeType - Exception in requestHealthDataHistoryAuthorization(): $e');
return false;
}
}

/// Requests permissions to access health data [types].
///
/// Returns true if successful, false otherwise.
Expand Down