Skip to content

Commit 74ee520

Browse files
authored
Merge pull request #1153 from cph-cachet/health-12/1127-hc-history
Health 12.1.0
2 parents 762fb20 + f2d6d0d commit 74ee520

File tree

8 files changed

+164
-18
lines changed

8 files changed

+164
-18
lines changed

packages/health/CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
## 12.0.2
1+
## 12.1.0
22

33
* iOS: Parse metadata to remove unsupported types - PR [#1120](https://github.com/cph-cachet/flutter-plugins/pull/1120)
44
* iOS: Add UV Index Types
5+
* 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)
6+
```XML
7+
<!-- Add the following permission into AndroidManifest.xml -->
8+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
9+
```
10+
* Android:
11+
* Update `androidx.compose:compose-bom` to `2025.02.00`
12+
* Update `androidx.health.connect:connect-client` to `1.1.0-alpha11`
13+
* Update `androidx.fragment:fragment-ktx` to `1.8.6`
14+
* Update to Java 11
515
* Update example apps
616

717
## 12.0.1

packages/health/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ An example of asking for permission to read and write heart rate data is shown b
7474
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
7575
```
7676

77+
By default, Health Connect restricts read data to 30 days from when permission has been granted.
78+
79+
You can check and request access to historical data using the `isHealthDataHistoryAuthorized` and `requestHealthDataHistoryAuthorization` methods, respectively.
80+
81+
The above methods require the following permission to be declared:
82+
83+
```xml
84+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
85+
```
86+
7787
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.
7888

7989
```xml

packages/health/android/build.gradle

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ apply plugin: 'com.android.library'
2525
apply plugin: 'kotlin-android'
2626

2727
android {
28-
compileSdkVersion 34
28+
compileSdk 34
2929

3030
compileOptions {
31-
sourceCompatibility JavaVersion.VERSION_1_8
32-
targetCompatibility JavaVersion.VERSION_1_8
31+
sourceCompatibility JavaVersion.VERSION_11
32+
targetCompatibility JavaVersion.VERSION_11
3333
}
3434

3535
kotlinOptions {
36-
jvmTarget = '1.8'
36+
jvmTarget = '11'
3737
}
3838

3939
sourceSets {
@@ -51,12 +51,12 @@ android {
5151
}
5252

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

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

6262
}

packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import android.util.Log
99
import androidx.activity.ComponentActivity
1010
import androidx.activity.result.ActivityResultLauncher
1111
import androidx.annotation.NonNull
12+
import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
1213
import androidx.health.connect.client.HealthConnectClient
14+
import androidx.health.connect.client.HealthConnectFeatures
1315
import androidx.health.connect.client.PermissionController
1416
import androidx.health.connect.client.permission.HealthPermission
17+
import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_HISTORY
1518
import androidx.health.connect.client.records.*
1619
import androidx.health.connect.client.records.MealType.MEAL_TYPE_BREAKFAST
1720
import androidx.health.connect.client.records.MealType.MEAL_TYPE_DINNER
@@ -147,6 +150,9 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
147150
when (call.method) {
148151
"installHealthConnect" -> installHealthConnect(call, result)
149152
"getHealthConnectSdkStatus" -> getHealthConnectSdkStatus(call, result)
153+
"isHealthDataHistoryAvailable" -> isHealthDataHistoryAvailable(call, result)
154+
"isHealthDataHistoryAuthorized" -> isHealthDataHistoryAuthorized(call, result)
155+
"requestHealthDataHistoryAuthorization" -> requestHealthDataHistoryAuthorization(call, result)
150156
"hasPermissions" -> hasPermissions(call, result)
151157
"requestAuthorization" -> requestAuthorization(call, result)
152158
"revokePermissions" -> revokePermissions(call, result)
@@ -512,6 +518,55 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
512518
}
513519
}
514520

521+
/**
522+
* Checks if the health data history feature is available on this device
523+
*/
524+
@OptIn(ExperimentalFeatureAvailabilityApi::class)
525+
private fun isHealthDataHistoryAvailable(call: MethodCall, result: Result) {
526+
scope.launch {
527+
result.success(
528+
healthConnectClient
529+
.features
530+
.getFeatureStatus(HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY) ==
531+
HealthConnectFeatures.FEATURE_STATUS_AVAILABLE)
532+
}
533+
}
534+
535+
/**
536+
* Checks if PERMISSION_READ_HEALTH_DATA_HISTORY has been granted
537+
*/
538+
private fun isHealthDataHistoryAuthorized(call: MethodCall, result: Result) {
539+
scope.launch {
540+
result.success(
541+
healthConnectClient
542+
.permissionController
543+
.getGrantedPermissions()
544+
.containsAll(listOf(PERMISSION_READ_HEALTH_DATA_HISTORY)),
545+
)
546+
}
547+
}
548+
549+
/**
550+
* Requests authorization for PERMISSION_READ_HEALTH_DATA_HISTORY
551+
*/
552+
private fun requestHealthDataHistoryAuthorization(call: MethodCall, result: Result) {
553+
if (context == null) {
554+
result.success(false)
555+
return
556+
}
557+
558+
if (healthConnectRequestPermissionsLauncher == null) {
559+
result.success(false)
560+
Log.i("FLUTTER_HEALTH", "Permission launcher not found")
561+
return
562+
}
563+
564+
// Store the result to be called in [onHealthConnectPermissionCallback]
565+
mResult = result
566+
isReplySubmitted = false
567+
healthConnectRequestPermissionsLauncher!!.launch(setOf(PERMISSION_READ_HEALTH_DATA_HISTORY))
568+
}
569+
515570
private fun hasPermissions(call: MethodCall, result: Result) {
516571
val args = call.arguments as HashMap<*, *>
517572
val types = (args["types"] as? ArrayList<*>)?.filterIsInstance<String>()!!

packages/health/example/android/app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
<uses-permission android:name="android.permission.health.READ_LEAN_BODY_MASS"/>
5555
<uses-permission android:name="android.permission.health.WRITE_LEAN_BODY_MASS"/>
5656

57+
<!-- For reading historical data - more than 30 days ago since permission given -->
58+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
59+
5760
<application
5861
android:label="health_example"
5962
android:name="${applicationName}"

packages/health/example/lib/main.dart

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ class HealthAppState extends State<HealthApp> {
122122
try {
123123
authorized =
124124
await health.requestAuthorization(types, permissions: permissions);
125+
126+
// request access to read historic data
127+
await health.requestHealthDataHistoryAuthorization();
128+
125129
} catch (error) {
126130
debugPrint("Exception in authorize: $error");
127131
}
@@ -289,10 +293,10 @@ class HealthAppState extends State<HealthApp> {
289293
startTime: earlier,
290294
endTime: now);
291295
success &= await health.writeHealthData(
292-
value: 22,
293-
type: HealthDataType.LEAN_BODY_MASS,
294-
startTime: earlier,
295-
endTime: now);
296+
value: 22,
297+
type: HealthDataType.LEAN_BODY_MASS,
298+
startTime: earlier,
299+
endTime: now);
296300

297301
// specialized write methods
298302
success &= await health.writeBloodOxygen(
@@ -400,11 +404,11 @@ class HealthAppState extends State<HealthApp> {
400404
endTime: now,
401405
recordingMethod: RecordingMethod.manual);
402406
success &= await health.writeHealthData(
403-
value: 4.3,
404-
type: HealthDataType.UV_INDEX,
405-
startTime: earlier,
406-
endTime: now,
407-
recordingMethod: RecordingMethod.manual);
407+
value: 4.3,
408+
type: HealthDataType.UV_INDEX,
409+
startTime: earlier,
410+
endTime: now,
411+
recordingMethod: RecordingMethod.manual);
408412
}
409413

410414
setState(() {

packages/health/ios/health.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
Pod::Spec.new do |s|
55
s.name = 'health'
6-
s.version = '12.0.2'
6+
s.version = '12.1.0'
77
s.summary = 'Wrapper for Apple\'s HealthKit on iOS and Google\'s Health Connect on Android.'
88
s.description = <<-DESC
99
Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android.

packages/health/lib/src/health_plugin.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,70 @@ class Health {
200200
}
201201
}
202202

203+
/// Checks if the Health Data History feature is available.
204+
///
205+
/// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY()
206+
///
207+
///
208+
/// Android only. Returns false on iOS or if an error occurs.
209+
Future<bool> isHealthDataHistoryAvailable() async {
210+
if (Platform.isIOS) return false;
211+
212+
try {
213+
final status =
214+
await _channel.invokeMethod<bool>('isHealthDataHistoryAvailable');
215+
return status ?? false;
216+
} catch (e) {
217+
debugPrint(
218+
'$runtimeType - Exception in isHealthDataHistoryAvailable(): $e');
219+
return false;
220+
}
221+
}
222+
223+
/// Checks the current status of the Health Data History permission.
224+
/// Make sure to check [isHealthConnectAvailable] before calling this method.
225+
///
226+
/// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY()
227+
///
228+
///
229+
/// Android only. Returns true on iOS or false if an error occurs.
230+
Future<bool> isHealthDataHistoryAuthorized() async {
231+
if (Platform.isIOS) return true;
232+
233+
try {
234+
final status =
235+
await _channel.invokeMethod<bool>('isHealthDataHistoryAuthorized');
236+
return status ?? false;
237+
} catch (e) {
238+
debugPrint(
239+
'$runtimeType - Exception in isHealthDataHistoryAuthorized(): $e');
240+
return false;
241+
}
242+
}
243+
244+
/// Requests the Health Data History permission.
245+
///
246+
/// Returns true if successful, false otherwise.
247+
///
248+
/// See this for more info: https://developer.android.com/reference/androidx/health/connect/client/permission/HealthPermission#PERMISSION_READ_HEALTH_DATA_HISTORY()
249+
///
250+
///
251+
/// Android only. Returns true on iOS or false if an error occurs.
252+
Future<bool> requestHealthDataHistoryAuthorization() async {
253+
if (Platform.isIOS) return true;
254+
255+
await _checkIfHealthConnectAvailableOnAndroid();
256+
try {
257+
final bool? isAuthorized =
258+
await _channel.invokeMethod('requestHealthDataHistoryAuthorization');
259+
return isAuthorized ?? false;
260+
} catch (e) {
261+
debugPrint(
262+
'$runtimeType - Exception in requestHealthDataHistoryAuthorization(): $e');
263+
return false;
264+
}
265+
}
266+
203267
/// Requests permissions to access health data [types].
204268
///
205269
/// Returns true if successful, false otherwise.

0 commit comments

Comments
 (0)