diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md
index 5b8107dd5..7e5a22404 100644
--- a/packages/health/CHANGELOG.md
+++ b/packages/health/CHANGELOG.md
@@ -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
+
+
+```
+* 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
diff --git a/packages/health/README.md b/packages/health/README.md
index f9c1ba9e6..559ec09c4 100644
--- a/packages/health/README.md
+++ b/packages/health/README.md
@@ -74,6 +74,16 @@ An example of asking for permission to read and write heart rate data is shown b
```
+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
+
+```
+
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
diff --git a/packages/health/android/build.gradle b/packages/health/android/build.gradle
index 1f96a1fb1..45c7b67d8 100644
--- a/packages/health/android/build.gradle
+++ b/packages/health/android/build.gradle
@@ -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 {
@@ -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"
}
diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt
index 1d6ee83dc..89f403722 100644
--- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt
+++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt
@@ -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
@@ -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)
@@ -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()!!
diff --git a/packages/health/example/android/app/src/main/AndroidManifest.xml b/packages/health/example/android/app/src/main/AndroidManifest.xml
index d76e5bb77..1b559506e 100644
--- a/packages/health/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/health/example/android/app/src/main/AndroidManifest.xml
@@ -54,6 +54,9 @@
+
+
+
{
try {
authorized =
await health.requestAuthorization(types, permissions: permissions);
+
+ // request access to read historic data
+ await health.requestHealthDataHistoryAuthorization();
+
} catch (error) {
debugPrint("Exception in authorize: $error");
}
@@ -289,10 +293,10 @@ class HealthAppState extends State {
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(
@@ -400,11 +404,11 @@ class HealthAppState extends State {
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(() {
diff --git a/packages/health/ios/health.podspec b/packages/health/ios/health.podspec
index 68bb13cf3..aba1806e8 100644
--- a/packages/health/ios/health.podspec
+++ b/packages/health/ios/health.podspec
@@ -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.
diff --git a/packages/health/lib/src/health_plugin.dart b/packages/health/lib/src/health_plugin.dart
index cc62e3ac7..c734533f0 100644
--- a/packages/health/lib/src/health_plugin.dart
+++ b/packages/health/lib/src/health_plugin.dart
@@ -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 isHealthDataHistoryAvailable() async {
+ if (Platform.isIOS) return false;
+
+ try {
+ final status =
+ await _channel.invokeMethod('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 isHealthDataHistoryAuthorized() async {
+ if (Platform.isIOS) return true;
+
+ try {
+ final status =
+ await _channel.invokeMethod('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 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.