Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alpha6 updates and Mapping Fixes #20

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
62b1759
update hc version and fix issues
rickcasson Oct 5, 2023
dc6db73
update alpha version
rickcasson Oct 18, 2023
96e3da5
update gradle and pull out stuff that was breaking
rickcasson Nov 1, 2023
0b1260b
fix offset parsing
rickcasson Nov 2, 2023
389c1a7
need to parse duration from zone offset
rickcasson Nov 2, 2023
234534c
Revert "need to parse duration from zone offset"
rickcasson Nov 2, 2023
48d76fd
add internal duration parser
rickcasson Nov 2, 2023
08110a0
Update blood_glucose_record.dart
rickcasson Nov 6, 2023
cfd4f8b
Update blood_glucose_record.dart
rickcasson Nov 9, 2023
c23f426
fix mapping
rickcasson Nov 10, 2023
3ca5e04
fix mapping
rickcasson Nov 8, 2023
aa0684f
fix assertion
rickcasson Nov 8, 2023
00b1d33
testing casting filter
rickcasson Nov 10, 2023
51dc9b3
fix datetime parsing
rickcasson Nov 10, 2023
3311df8
remove logging
rickcasson Nov 10, 2023
1bc0d30
remove package info
rickcasson Nov 10, 2023
99b5eef
remove print
rickcasson Nov 10, 2023
6f036e8
update ChangeLog
rickcasson Nov 10, 2023
ab381a8
fix blood pressure mapping
rickcasson Nov 15, 2023
fde95ee
fix unit conversions
rickcasson Nov 15, 2023
578b43c
Update FlutterHealthConnectPlugin.kt
rickcasson Nov 17, 2023
57b859d
fixing mapping
rickcasson Nov 17, 2023
8153b2f
fix type mapping
rickcasson Nov 17, 2023
1916dce
fix other maps
rickcasson Nov 17, 2023
8597a24
add delete records logs
rickcasson Nov 18, 2023
a705a6b
just reply true
rickcasson Nov 18, 2023
b57f652
clean up logs
rickcasson Nov 18, 2023
ba59e36
Fixes to support Android 14
rickcasson Nov 21, 2023
d937e1b
refactor timezone offset parsing
rickcasson Dec 4, 2023
a08521f
Remove old sleep stage record
rickcasson Dec 15, 2023
c20aa7b
Add more control over permission types
rickcasson Dec 15, 2023
9a1a47b
Update example and readme
rickcasson Dec 19, 2023
c6a5479
roll back gradle changes and only include used permissions in Android…
rickcasson Dec 20, 2023
64bdaa9
Added revokeAllPermissions method and update readme
rickcasson Dec 20, 2023
92dd286
Fixed key / value issue in sleep stage map - key is 'stage', not 'type'.
thildebrand1 Feb 1, 2024
56b1059
Update sleep_session_record.dart
thildebrand1 Feb 1, 2024
8e89874
Merge pull request #1 from thildebrand1/alpha6-updates
rickcasson Feb 1, 2024
5abb0d4
rename stage variable for consistancy
rickcasson Feb 1, 2024
9b43296
fix has permission crash
rickcasson Feb 15, 2024
3503c5e
fix startTime assert to allow for same endTime
rickcasson Apr 15, 2024
3c57d9e
fix carbohydrates mapping
rickcasson Apr 22, 2024
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## [2.2.0]
* Upgrade Health Connect API to `1.1.0-alpha6`
* Add type filtering for getRecords
* Modify requestPermissions and hasPermissions to allow for read and write only permissions
* Fixed various mapping issues
* Add revokeAllPermissions method
* Updated README.md
## [2.0.0]
* Added missing data types
* Added Models for each data type
Expand Down
136 changes: 123 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,79 @@ To create the declaration, add to regular permissions any of.
<uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES"/>
<uses-permission android:name="android.permission.health.WRITE_WHEELCHAIR_PUSHES"/>
```
Inside your MainActivity declaration add a reference to `health_permissions` and an intent filter for the Health Connect permissions action
Below your MainActivity declaration, add the following intent filters for when the user clicks the privacy policy link:
```
<activity android:name=".MainActivity">
<meta-data android:name="health_permissions" android:resource="@array/health_permissions" />
<!-- For supported versions through Android 13, create an activity to show the rationale
of Health Connect permissions once users click the privacy policy link. -->
<activity
android:name=".PermissionsRationaleActivity"
android:exported="true">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE"/>
</intent-filter>
</activity>

<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>
<!-- For versions starting Android 14, create an activity alias to show the rationale
of Health Connect permissions once users click the privacy policy link. -->
<activity-alias
android:name="AndroidURationaleActivity"
android:exported="true"
android:targetActivity=".PermissionsRationaleActivity"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>
```

Health connect developer toolbox: http://goo.gle/health-connect-toolbox
You will then need to create the PermissionsRationaleActivity class to open privacy policy link.

Kotlin:
```
package [your_package_name]

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle

class PermissionsRationaleActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("[your_privacy_policy_url]")))
}
}
```

Java:
```
package [your_package_name];

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

public class PermissionsRationaleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("[your_privacy_policy_url]")));
}
}
```

Note: For your app to work on Android 14 you will need to ensure your MainActivity class extends `FlutterFragmentActivity` instead of `FlutterActivity`

If you are using ProGuard, you will need to add the following to your proguard-rules file:
```
-keep public class androidx.health.** { *; }
-dontwarn androidx.health.**
```

Health Connect developer toolbox: http://goo.gle/health-connect-toolbox


## Usage
```dart
Expand Down Expand Up @@ -167,7 +228,6 @@ class _MyAppState extends State<MyApp> {
// HealthConnectDataType.RestingHeartRate,
// HealthConnectDataType.SexualActivity,
// HealthConnectDataType.SleepSession,
// HealthConnectDataType.SleepStage,
// HealthConnectDataType.Speed,
// HealthConnectDataType.StepsCadence,
// HealthConnectDataType.Steps,
Expand All @@ -180,12 +240,21 @@ class _MyAppState extends State<MyApp> {
List<HealthConnectDataType> types = [
HealthConnectDataType.Steps,
HealthConnectDataType.ExerciseSession,
HealthConnectDataType.TotalCaloriesBurned,
// HealthConnectDataType.HeartRate,
// HealthConnectDataType.SleepSession,
// HealthConnectDataType.OxygenSaturation,
// HealthConnectDataType.RespiratoryRate,
];

List<HealthConnectDataType> readOnlyTypes = [
HealthConnectDataType.Height,
];

List<HealthConnectDataType> writeOnlyTypes = [
HealthConnectDataType.Weight,
];

bool readOnly = true;
String resultText = '';

Expand Down Expand Up @@ -245,7 +314,8 @@ class _MyAppState extends State<MyApp> {
onPressed: () async {
var result = await HealthConnectFactory.hasPermissions(
types,
readOnly: readOnly,
readOnlyTypes: readOnlyTypes,
writeOnlyTypes: writeOnlyTypes,
);
resultText = 'hasPermissions: $result';
_updateResultText();
Expand Down Expand Up @@ -281,7 +351,8 @@ class _MyAppState extends State<MyApp> {
try {
var result = await HealthConnectFactory.requestPermissions(
types,
//readOnly: readOnly,
readOnlyTypes: readOnlyTypes,
writeOnlyTypes: writeOnlyTypes,
);
resultText = 'requestPermissions: $result';
} catch (e) {
Expand All @@ -291,6 +362,20 @@ class _MyAppState extends State<MyApp> {
},
child: const Text('Request Permissions'),
),
ElevatedButton(
onPressed: () async {
try {
// This will only apply after the app has been fully closed.
var result =
await HealthConnectFactory.revokeAllPermissions();
resultText = 'revokeAllPermissions: $result';
} catch (e) {
resultText = e.toString();
}
_updateResultText();
},
child: const Text('Revoke All Permissions'),
),
ElevatedButton(
onPressed: () async {
var startTime =
Expand All @@ -299,7 +384,11 @@ class _MyAppState extends State<MyApp> {
try {
final requests = <Future>[];
Map<String, dynamic> typePoints = {};
for (var type in types) {
List<HealthConnectDataType> readTypes = [
...types,
...readOnlyTypes
];
for (var type in readTypes) {
requests.add(HealthConnectFactory.getRecords(
type: type,
startTime: startTime,
Expand Down Expand Up @@ -331,6 +420,16 @@ class _MyAppState extends State<MyApp> {
endTime: endTime,
exerciseType: ExerciseType.walking,
);
TotalCaloriesBurnedRecord totalCaloriesBurned =
TotalCaloriesBurnedRecord(
startTime: startTime,
endTime: endTime,
energy: const Energy.kilocalories(5),
);
WeightRecord weightRecord = WeightRecord(
time: startTime,
weight: const Mass.kilograms(60),
);
try {
final requests = <Future>[];
Map<String, dynamic> typePoints = {};
Expand All @@ -339,14 +438,25 @@ class _MyAppState extends State<MyApp> {
data: [stepsRecord],
).then((value) => typePoints.addAll(
{HealthConnectDataType.Steps.name: stepsRecord})));

requests.add(HealthConnectFactory.writeData(
type: HealthConnectDataType.ExerciseSession,
data: [exerciseSessionRecord],
).then((value) => typePoints.addAll({
HealthConnectDataType.ExerciseSession.name:
exerciseSessionRecord
})));
requests.add(HealthConnectFactory.writeData(
type: HealthConnectDataType.TotalCaloriesBurned,
data: [totalCaloriesBurned],
).then((value) => typePoints.addAll({
HealthConnectDataType.TotalCaloriesBurned.name:
totalCaloriesBurned
})));
requests.add(HealthConnectFactory.writeData(
type: HealthConnectDataType.Weight,
data: [weightRecord],
).then((value) => typePoints.addAll(
{HealthConnectDataType.Weight.name: weightRecord})));
await Future.wait(requests);
resultText = '$typePoints';
} catch (e, s) {
Expand Down
9 changes: 6 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ group 'dev.duynp.flutter_health_connect'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.8.22'
ext.kotlin_version = '1.9.10'
repositories {
google()
mavenCentral()
Expand All @@ -25,7 +25,9 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 34
compileSdk 34

namespace = "dev.duynp.flutter_health_connect"

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -47,7 +49,8 @@ android {

dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
implementation "androidx.health.connect:connect-client:1.1.0-alpha02"
implementation "androidx.health.connect:connect-client:1.1.0-alpha06"
implementation "androidx.fragment:fragment-ktx:1.6.2"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.0'
Expand Down
31 changes: 21 additions & 10 deletions android/src/main/kotlin/dev/duynp/flutter_health_connect/Consts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const val RESPIRATORY_RATE = "RespiratoryRate"
const val RESTING_HEART_RATE = "RestingHeartRate"
const val SEXUAL_ACTIVITY = "SexualActivity"
const val SLEEP_SESSION = "SleepSession"
const val SLEEP_STAGE = "SleepStage"
const val SPEED = "Speed"
const val STEPS_CADENCE = "StepsCadence"
const val STEPS = "Steps"
Expand Down Expand Up @@ -75,7 +74,6 @@ val HealthConnectRecordTypeMap = hashMapOf(
RESTING_HEART_RATE to RestingHeartRateRecord::class,
SEXUAL_ACTIVITY to SexualActivityRecord::class,
SLEEP_SESSION to SleepSessionRecord::class,
SLEEP_STAGE to SleepStageRecord::class,
SPEED to SpeedRecord::class,
STEPS_CADENCE to StepsCadenceRecord::class,
STEPS to StepsRecord::class,
Expand All @@ -85,24 +83,37 @@ val HealthConnectRecordTypeMap = hashMapOf(
WHEELCHAIR_PUSHES to WheelchairPushesRecord::class,
)
const val HEALTH_CONNECT_RESULT_CODE = 16969
const val MAX_LENGTH = 9999999
const val MAX_LENGTH = 5000

fun mapTypesToPermissions(
types: List<String>?,
readOnly: Boolean
bothTypes: List<String>?,
readTypes: List<String>?,
writeTypes: List<String>?
): MutableSet<String
> {
val permissions = mutableSetOf<String>()
if (types != null) {
for (item: String in types) {
if (bothTypes != null) {
for (item: String in bothTypes) {
HealthConnectRecordTypeMap[item]?.let { classType ->
if (!readOnly) {
permissions.add(HealthPermission.getWritePermission(classType))
}
permissions.add(HealthPermission.getWritePermission(classType))
permissions.add(HealthPermission.getReadPermission(classType))
}
}
}
if (readTypes != null) {
for (item: String in readTypes) {
HealthConnectRecordTypeMap[item]?.let { classType ->
permissions.add(HealthPermission.getReadPermission(classType))
}
}
}
if (writeTypes != null) {
for (item: String in writeTypes) {
HealthConnectRecordTypeMap[item]?.let { classType ->
permissions.add(HealthPermission.getWritePermission(classType))
}
}
}
return permissions
}

Expand Down
Loading