From 15555248a081dbd4ad6486b917c26a885c558b18 Mon Sep 17 00:00:00 2001
From: sztyr <54115527+sztyr@users.noreply.github.com>
Date: Fri, 19 Nov 2021 01:26:31 +0100
Subject: [PATCH 01/30] Add the android.permission.ACTIVITY_RECOGNITION setup
to the README
---
packages/health/README.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/packages/health/README.md b/packages/health/README.md
index 32692b9bc..5b683a57f 100644
--- a/packages/health/README.md
+++ b/packages/health/README.md
@@ -94,6 +94,27 @@ Follow the instructions at https://console.developers.google.com/flows/enableapi
The client id will look something like `YOUR_CLIENT_ID.apps.googleusercontent.com`
+### Android Permissions
+
+Starting from API level 28 (Android 9.0) acessing some fitness data (e.g. Steps) requires a special permission.
+
+To set it add the following line to your `AndroidManifest.xml` file.
+```
+
+```
+
+There's a `debug`, `main` and `profile` version which are chosen depending on how you start your app. In general, it's sufficient to add permission only to the `main` version.
+
+Beacuse this is a `dangerous` protectionLevel permission system will not grant it automaticlly and it requires user action.
+
+You can prompt the user for it using the [permission_handler](https://pub.dev/packages/permission_handler) plugin.
+Follow the plugin setup instructions and add the following line before requsting the data:
+
+ ```
+ await Permission.activityRecognition.request();
+ ```
+
+
### Android X
Replace the content of the `android/gradle.properties` file with the following lines:
From 50f3955b11a3e80f3a58f360106fce8f39c2e166 Mon Sep 17 00:00:00 2001
From: PinkyUni
Date: Fri, 3 Dec 2021 11:33:04 +0300
Subject: [PATCH 02/30] feat: Add method for calculating total steps
---
.../cachet/plugins/health/HealthPlugin.kt | 85 +++++++++++++++++++
.../example/.flutter-plugins-dependencies | 2 +-
.../ios/Classes/SwiftHealthPlugin.swift | 41 +++++++++
packages/health/lib/src/health_factory.dart | 16 ++++
4 files changed, 143 insertions(+), 1 deletion(-)
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 12c76b0a4..09ed16712 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
@@ -420,12 +420,97 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
}
}
+ fun getTotalStepsInInterval(call: MethodCall, result: Result) {
+ val start = call.argument("startDate")!!
+ val end = call.argument("endDate")!!
+
+ getStepsInRange(start, end) { map: Map?, e: Throwable? ->
+ if (map != null) {
+ assert(map.size <= 1) { "getTotalStepsInInterval should return only one interval. Found: ${map.size}" }
+ result.success(map.values.firstOrNull())
+ } else {
+ result.error("failed", e?.message, null)
+ }
+ }
+ }
+
+ private fun getStepsInRange(
+ start: Long,
+ end: Long,
+ result: (Map?, Throwable?) -> Unit
+ ) {
+ val activity = activity ?: return
+
+ val stepsDataType = keyToHealthDataType(STEPS)
+ val aggregatedDataType = keyToHealthDataType(AGGREGATE_STEP_COUNT)
+
+ val fitnessOptions = FitnessOptions.builder()
+ .addDataType(stepsDataType)
+ .addDataType(aggregatedDataType)
+ .build()
+ val gsa = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
+
+ val ds = DataSource.Builder()
+ .setAppPackageName("com.google.android.gms")
+ .setDataType(stepsDataType)
+ .setType(DataSource.TYPE_DERIVED)
+ .setStreamName("estimated_steps")
+ .build()
+
+ val duration = (end - start).toInt()
+
+ val request = DataReadRequest.Builder()
+ .aggregate(ds)
+ .bucketByTime(duration, TimeUnit.MILLISECONDS)
+ .setTimeRange(start, end, TimeUnit.MILLISECONDS)
+ .build()
+
+ val response = Fitness.getHistoryClient(activity, gsa).readData(request)
+
+ Thread {
+ try {
+ val readDataResult = Tasks.await(response)
+
+ val map = HashMap() // need to return to Dart so can't use sparse array
+ for (bucket in readDataResult.buckets) {
+ val dp = bucket.dataSets.firstOrNull()?.dataPoints?.firstOrNull()
+ if (dp != null) {
+ print(dp)
+
+ val count = dp.getValue(aggregatedDataType.fields[0])
+
+ val startTime = dp.getStartTime(TimeUnit.MILLISECONDS)
+ val startDate = Date(startTime)
+ val endDate = Date(dp.getEndTime(TimeUnit.MILLISECONDS))
+ Log.i("FLUTTER_HEALTH::SUCCESS", "returning $count steps for $startDate - $endDate")
+ map[startTime] = count.asInt()
+ } else {
+ val startDay = Date(start)
+ val endDay = Date(end)
+ Log.i("FLUTTER_HEALTH::ERROR", "no steps for $startDay - $endDay")
+ }
+ }
+ activity.runOnUiThread {
+ result(map, null)
+ }
+ } catch (e: Throwable) {
+ Log.e("FLUTTER_HEALTH::ERROR", "failed: ${e.message}")
+
+ activity.runOnUiThread {
+ result(null, e)
+ }
+ }
+
+ }.start()
+ }
+
/// Handle calls from the MethodChannel
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"requestAuthorization" -> requestAuthorization(call, result)
"getData" -> getData(call, result)
"writeData" -> writeData(call, result)
+ "getTotalStepsInInterval" -> getTotalStepsInInterval(call, result)
else -> result.notImplemented()
}
}
diff --git a/packages/health/example/.flutter-plugins-dependencies b/packages/health/example/.flutter-plugins-dependencies
index aac5d2cc2..37ecaab6b 100644
--- a/packages/health/example/.flutter-plugins-dependencies
+++ b/packages/health/example/.flutter-plugins-dependencies
@@ -1 +1 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_macos-2.2.0/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_web-2.1.0/","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-11-18 09:57:35.099678","version":"2.5.3"}
\ No newline at end of file
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus-3.1.1\\\\","dependencies":[]},{"name":"health","path":"D:\\\\SheepApps\\\\Flutter\\\\flutter-plugins\\\\packages\\\\health\\\\","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus-3.1.1\\\\","dependencies":[]},{"name":"health","path":"D:\\\\SheepApps\\\\Flutter\\\\flutter-plugins\\\\packages\\\\health\\\\","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus_macos-2.2.0\\\\","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus_web-2.1.0\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-12-03 11:04:00.730455","version":"2.5.3"}
\ No newline at end of file
diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift
index 9aa8dc264..c7930a947 100644
--- a/packages/health/ios/Classes/SwiftHealthPlugin.swift
+++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift
@@ -73,6 +73,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
getData(call: call, result: result)
}
+ /// Handle getTotalStepsInInterval
+ else if (call.method.elementsEqual("getTotalStepsInInterval")){
+ getTotalStepsInInterval(call: call, result: result)
+ }
+
/// Handle writeData
else if (call.method.elementsEqual("writeData")){
writeData(call: call, result: result)
@@ -216,6 +221,42 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
HKHealthStore().execute(query)
}
+ func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) {
+ let arguments = call.arguments as! [String: Int]
+ let startMillis = arguments["startDate"]!
+ let endMillis = arguments["endDate"]!
+
+ let startDate = Date(timeIntervalSince1970: startMillis.toTimeInterval)
+ let endDate = Date(timeIntervalSince1970: endMillis.toTimeInterval)
+ let sampleType = dataTypeLookUp(key: STEPS)
+ let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [.strictStartDate])
+
+ let query = HKStatisticsQuery(quantityType: sampleType,
+ quantitySamplePredicate: predicate,
+ options: .cumulativeSum) { query, queryResult, error in
+
+ guard let queryResult = queryResult else {
+ let error = error! as NSError
+ print("[getTotalStepsInInterval] got error: \(error)")
+ result(FlutterError(code: "\(error.code)", message: error.domain, details: error.localizedDescription))
+ return
+ }
+
+ var steps = 0.0
+
+ if let quantity = queryResult.sumQuantity() {
+ let unit = HKUnit.count()
+ steps = quantity.doubleValue(for: unit)
+ print("Amount of steps: \(steps), since: \(queryResult.startDate) until: \(queryResult.endDate)")
+ }
+
+ let totalSteps = Int(steps)
+ result(totalSteps)
+ }
+
+ HKHealthStore().execute(query)
+ }
+
func unitLookUp(key: String) -> HKUnit {
guard let unit = unitDict[key] else {
return HKUnit.count()
diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart
index 3bcbc43fd..11c0f8029 100644
--- a/packages/health/lib/src/health_factory.dart
+++ b/packages/health/lib/src/health_factory.dart
@@ -214,4 +214,20 @@ class HealthFactory {
}
return unique;
}
+
+ Future> getTotalStepsCount(
+ DateTime startDate,
+ DateTime endDate,
+ ) async {
+ // Set parameters for method channel request
+ final args = {
+ 'startDate': startDate.millisecondsSinceEpoch,
+ 'endDate': endDate.millisecondsSinceEpoch
+ };
+ final stepsCount = await _channel.invokeMethod(
+ 'getTotalStepsInInterval',
+ args,
+ );
+ return stepsCount;
+ }
}
From e324a9e79041d3a3872202b570edcfa5e0f16128 Mon Sep 17 00:00:00 2001
From: PinkyUni
Date: Fri, 3 Dec 2021 11:49:04 +0300
Subject: [PATCH 03/30] feat: Fix return type
---
packages/health/lib/src/health_factory.dart | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart
index 11c0f8029..5e0d1b862 100644
--- a/packages/health/lib/src/health_factory.dart
+++ b/packages/health/lib/src/health_factory.dart
@@ -215,7 +215,7 @@ class HealthFactory {
return unique;
}
- Future> getTotalStepsCount(
+ Future getTotalStepsCount(
DateTime startDate,
DateTime endDate,
) async {
From f984cf7f8c952f7446a7cdcf7f10a14400c104d8 Mon Sep 17 00:00:00 2001
From: PinkyUni
Date: Fri, 3 Dec 2021 11:49:42 +0300
Subject: [PATCH 04/30] feat: Rename method
---
packages/health/lib/src/health_factory.dart | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart
index 5e0d1b862..3a0a9330a 100644
--- a/packages/health/lib/src/health_factory.dart
+++ b/packages/health/lib/src/health_factory.dart
@@ -215,7 +215,7 @@ class HealthFactory {
return unique;
}
- Future getTotalStepsCount(
+ Future getTotalStepsInInterval(
DateTime startDate,
DateTime endDate,
) async {
From 2306c3f810d941cf55feea3c3967f783b9cfa0cc Mon Sep 17 00:00:00 2001
From: PinkyUni
Date: Fri, 3 Dec 2021 12:24:48 +0300
Subject: [PATCH 05/30] feat: Fix issues
---
.../src/main/kotlin/cachet/plugins/health/HealthPlugin.kt | 3 +++
1 file changed, 3 insertions(+)
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 09ed16712..629102b68 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
@@ -18,6 +18,7 @@ import android.util.Log
import androidx.annotation.NonNull
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener
import java.util.concurrent.TimeUnit
+import java.util.Date
import kotlin.concurrent.thread
import com.google.android.gms.fitness.data.*
import com.google.android.gms.fitness.request.SessionReadRequest
@@ -39,6 +40,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
private var HEIGHT = "HEIGHT"
private var WEIGHT = "WEIGHT"
private var STEPS = "STEPS"
+ private var AGGREGATE_STEP_COUNT = "AGGREGATE_STEP_COUNT"
private var ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED"
private var HEART_RATE = "HEART_RATE"
private var BODY_TEMPERATURE = "BODY_TEMPERATURE"
@@ -141,6 +143,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
HEIGHT -> DataType.TYPE_HEIGHT
WEIGHT -> DataType.TYPE_WEIGHT
STEPS -> DataType.TYPE_STEP_COUNT_DELTA
+ AGGREGATE_STEP_COUNT -> DataType.AGGREGATE_STEP_COUNT_DELTA
ACTIVE_ENERGY_BURNED -> DataType.TYPE_CALORIES_EXPENDED
HEART_RATE -> DataType.TYPE_HEART_RATE_BPM
BODY_TEMPERATURE -> HealthDataTypes.TYPE_BODY_TEMPERATURE
From c34b08ee33a63b9b400a941fbe3af68c4af0df5f Mon Sep 17 00:00:00 2001
From: PinkyUni
Date: Fri, 3 Dec 2021 13:20:30 +0300
Subject: [PATCH 06/30] feat: Fix ios issues
---
.../health/ios/Classes/SwiftHealthPlugin.swift | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift
index c7930a947..4631b9f2c 100644
--- a/packages/health/ios/Classes/SwiftHealthPlugin.swift
+++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift
@@ -223,13 +223,15 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as! [String: Int]
- let startMillis = arguments["startDate"]!
- let endMillis = arguments["endDate"]!
+ let startDate = (arguments?["startDate"] as? NSNumber) ?? 0
+ let endDate = (arguments?["endDate"] as? NSNumber) ?? 0
+
+ // Convert dates from milliseconds to Date()
+ let dateFrom = Date(timeIntervalSince1970: startDate.doubleValue / 1000)
+ let dateTo = Date(timeIntervalSince1970: endDate.doubleValue / 1000)
- let startDate = Date(timeIntervalSince1970: startMillis.toTimeInterval)
- let endDate = Date(timeIntervalSince1970: endMillis.toTimeInterval)
let sampleType = dataTypeLookUp(key: STEPS)
- let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [.strictStartDate])
+ let predicate = HKQuery.predicateForSamples(withStart: dateFrom, end: dateTo, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: sampleType,
quantitySamplePredicate: predicate,
@@ -237,7 +239,6 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
guard let queryResult = queryResult else {
let error = error! as NSError
- print("[getTotalStepsInInterval] got error: \(error)")
result(FlutterError(code: "\(error.code)", message: error.domain, details: error.localizedDescription))
return
}
@@ -247,7 +248,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
if let quantity = queryResult.sumQuantity() {
let unit = HKUnit.count()
steps = quantity.doubleValue(for: unit)
- print("Amount of steps: \(steps), since: \(queryResult.startDate) until: \(queryResult.endDate)")
+ print("Amount of steps: \(steps)")
}
let totalSteps = Int(steps)
From 9726e6d4f11a4b6ccfec8022a7f52a4128d46263 Mon Sep 17 00:00:00 2001
From: PinkyUni
Date: Fri, 3 Dec 2021 16:59:11 +0300
Subject: [PATCH 07/30] feat: Fix some swift issues
---
packages/health/ios/Classes/SwiftHealthPlugin.swift | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift
index 4631b9f2c..d11fb9995 100644
--- a/packages/health/ios/Classes/SwiftHealthPlugin.swift
+++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift
@@ -222,7 +222,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
}
func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) {
- let arguments = call.arguments as! [String: Int]
+ let arguments = call.arguments as? NSDictionary
let startDate = (arguments?["startDate"] as? NSNumber) ?? 0
let endDate = (arguments?["endDate"] as? NSNumber) ?? 0
@@ -230,7 +230,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
let dateFrom = Date(timeIntervalSince1970: startDate.doubleValue / 1000)
let dateTo = Date(timeIntervalSince1970: endDate.doubleValue / 1000)
- let sampleType = dataTypeLookUp(key: STEPS)
+ let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let predicate = HKQuery.predicateForSamples(withStart: dateFrom, end: dateTo, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: sampleType,
From a924c3fda82616a72df8de0a07b13834a2ade7e4 Mon Sep 17 00:00:00 2001
From: altanod
Date: Tue, 14 Dec 2021 11:11:43 +0800
Subject: [PATCH 08/30] fix: android 12 intent-flag
---
.../ActivityRecognitionFlutterPlugin.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
index 67d70be7f..9f02e5dcb 100644
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
+++ b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
@@ -45,7 +45,11 @@ public class ActivityRecognitionFlutterPlugin implements FlutterPlugin, EventCha
private void startActivityTracking() {
// Start the service
Intent intent = new Intent(androidActivity, ActivityRecognizedBroadcastReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(androidActivity, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= 23) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ }
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(androidActivity, 0, intent, flags);
// Frequency in milliseconds
long frequency = 5 * 1000;
From 1234a70d7a26d09118c8defefc4368b3bc3fe4b1 Mon Sep 17 00:00:00 2001
From: bardram
Date: Wed, 29 Dec 2021 18:08:40 +0100
Subject: [PATCH 09/30] merge of PR #457 and #462
---
packages/health/CHANGELOG.md | 4 ++++
.../main/kotlin/cachet/plugins/health/HealthPlugin.kt | 9 ---------
2 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md
index dd43a2865..aa59d17b4 100644
--- a/packages/health/CHANGELOG.md
+++ b/packages/health/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.3.2
+* [PR #457](https://github.com/cph-cachet/flutter-plugins/pull/457) - Add sleep in bed to android
+* [PR #462](https://github.com/cph-cachet/flutter-plugins/pull/462) - Fixed (regression) issues with metric and permissions
+
## 3.3.1
* [PR #428](https://github.com/cph-cachet/flutter-plugins/pull/428) - DISTANCE_DELTA is for Android, not iOS
* [PR #454](https://github.com/cph-cachet/flutter-plugins/pull/454) - added missing READ_ACCESS
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 5f7d03459..8094ab567 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
@@ -423,7 +423,6 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
private fun callToHealthTypes(call: MethodCall): FitnessOptions {
val typesBuilder = FitnessOptions.builder()
val args = call.arguments as HashMap<*, *>
-//<<<<<<< metric
val types = (args["types"] as? ArrayList<*>)?.filterIsInstance()
val permissions = (args["permissions"] as? ArrayList<*>)?.filterIsInstance()
@@ -443,15 +442,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
}
else -> throw IllegalArgumentException("Unknown access type $access")
}
- if (typeKey == SLEEP_ASLEEP || typeKey == SLEEP_AWAKE) {
-//=======
- val types = args["types"] as ArrayList<*>
- for (typeKey in types) {
- if (typeKey !is String) continue
- typesBuilder.addDataType(keyToHealthDataType(typeKey), FitnessOptions.ACCESS_READ)
- typesBuilder.addDataType(keyToHealthDataType(typeKey), FitnessOptions.ACCESS_WRITE)
if (typeKey == SLEEP_ASLEEP || typeKey == SLEEP_AWAKE || typeKey == SLEEP_IN_BED) {
-//>>>>>>> master
typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
when (access) {
0 -> typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
From 765796cdecef60e1ec08af7dc6308f5e727697f6 Mon Sep 17 00:00:00 2001
From: bardram
Date: Wed, 29 Dec 2021 21:13:58 +0100
Subject: [PATCH 10/30] Update of example app and API docs
---
.gitignore | 1 +
packages/health/CHANGELOG.md | 6 +-
packages/health/README.md | 61 ++++--
.../example/.flutter-plugins-dependencies | 2 +-
.../ios/Flutter/flutter_export_environment.sh | 11 +-
.../ios/Runner.xcodeproj/project.pbxproj | 10 +-
.../ios/Runner/RunnerDebug.entitlements | 10 +
packages/health/example/lib/main.dart | 187 +++++++++++-------
packages/health/example/pubspec.yaml | 1 +
.../ios/Classes/SwiftHealthPlugin.swift | 1 -
packages/health/lib/src/data_types.dart | 1 +
packages/health/lib/src/health_factory.dart | 73 +++----
packages/health/pubspec.yaml | 2 +-
13 files changed, 235 insertions(+), 131 deletions(-)
create mode 100644 packages/health/example/ios/Runner/RunnerDebug.entitlements
diff --git a/.gitignore b/.gitignore
index 7ca4f00c7..86e4f2f17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -122,3 +122,4 @@ packages/audio_streamer/example/.flutter-plugins-dependencies
*.sh
packages/health/example/.flutter-plugins-dependencies
*.sh
+packages/health/example/.flutter-plugins-dependencies
diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md
index aa59d17b4..5451eba4e 100644
--- a/packages/health/CHANGELOG.md
+++ b/packages/health/CHANGELOG.md
@@ -1,6 +1,10 @@
-## 3.3.2
+## 3.4.0
* [PR #457](https://github.com/cph-cachet/flutter-plugins/pull/457) - Add sleep in bed to android
+* [PR #458](https://github.com/cph-cachet/flutter-plugins/pull/458) - Add the android.permission.ACTIVITY_RECOGNITION setup to the README
* [PR #462](https://github.com/cph-cachet/flutter-plugins/pull/462) - Fixed (regression) issues with metric and permissions
+* [PR #471](https://github.com/cph-cachet/flutter-plugins/pull/471) - Get total steps
+* update of example app to refelct new features.
+* update of API documentation.
## 3.3.1
* [PR #428](https://github.com/cph-cachet/flutter-plugins/pull/428) - DISTANCE_DELTA is for Android, not iOS
diff --git a/packages/health/README.md b/packages/health/README.md
index 5b683a57f..bbec641ce 100644
--- a/packages/health/README.md
+++ b/packages/health/README.md
@@ -1,14 +1,12 @@
# Health
-This library combines both GoogleFit and AppleHealthKit. It support most of the values provided.
+Enables reading and writing health data from/to Apple Health and Google Fit. Also allow for getting the total number of steps for a specific time period.
-Supports **iOS** and **Android X**
-
-NB: For Android, your app _needs_ to have Google Fit installed and have access to the internet, otherwise this plugin will not work.
+> For Android, the target phone __needs__ to have [Google Fit](https://www.google.com/fit/) installed and have access to the internet, otherwise this plugin will not work.
## Data Types
-| **Data Type** | **Unit** | **iOS** | **Android** | **Comments** |
+| **Data Type** | **Unit** | **iOS** | **Android** | **Comments** |
| --------------------------- | ----------------------- | ----------- | ------------ | ----------------------------------------------------------- |
| ACTIVE_ENERGY_BURNED | CALORIES | yes | yes | |
| BASAL_ENERGY_BURNED | CALORIES | yes | | |
@@ -46,7 +44,7 @@ NB: For Android, your app _needs_ to have Google Fit installed and have access t
## Setup
-### Apple HealthKit (iOS)
+### Apple Health (iOS)
Step 1: Append the Info.plist with the following 2 entries
@@ -92,21 +90,21 @@ Certificate fingerprints:
Follow the instructions at https://console.developers.google.com/flows/enableapi?apiid=fitness for setting up an OAuth2 Client ID for a Google project, and adding the SHA1 fingerprint to that OAuth2 credential.
-The client id will look something like `YOUR_CLIENT_ID.apps.googleusercontent.com`
+The client id will look something like `YOUR_CLIENT_ID.apps.googleusercontent.com`.
### Android Permissions
Starting from API level 28 (Android 9.0) acessing some fitness data (e.g. Steps) requires a special permission.
To set it add the following line to your `AndroidManifest.xml` file.
+
```
```
There's a `debug`, `main` and `profile` version which are chosen depending on how you start your app. In general, it's sufficient to add permission only to the `main` version.
-Beacuse this is a `dangerous` protectionLevel permission system will not grant it automaticlly and it requires user action.
-
+Beacuse this is labled as a `dangerous` protection level, the permission system will not grant it automaticlly and it requires the user's action.
You can prompt the user for it using the [permission_handler](https://pub.dev/packages/permission_handler) plugin.
Follow the plugin setup instructions and add the following line before requsting the data:
@@ -127,9 +125,50 @@ android.useAndroidX=true
## Usage
-Below is a snippet from the `example app` showing the plugin in use.
+See the example app for detailed examples of how to use the Health API.
+
+The Health plugin is used via the `HealthFactory` class using the different methods for handling permissions and getting and adding data to Apple Health / Google Fit.
+Below is a simplified flow of how to use the plugin.
+
+```dart
+ // create a HealthFactory for use in the app
+ HealthFactory health = HealthFactory();
+
+ // define the types to get
+ var types = [
+ HealthDataType.STEPS,
+ HealthDataType.WEIGHT,
+ HealthDataType.HEIGHT,
+ HealthDataType.BLOOD_GLUCOSE,
+ ];
+
+ // requesting access to the data types before reading them
+ bool requested = await health.requestAuthorization(types);
+
+ var now = DateTime.now();
+
+ // fetch health data from the last 24 hours
+ List healthData = await health.getHealthDataFromTypes(
+ now.subtract(Duration(days: 1)), now, types);
+
+ // request permissions to write steps and blood glucose
+ types = [HealthDataType.STEPS, HealthDataType.BLOOD_GLUCOSE];
+ var permissions = [
+ HealthDataAccess.READ_WRITE,
+ HealthDataAccess.READ_WRITE
+ ];
+ await health.requestAuthorization(types, permissions: permissions);
+
+ // write steps and blood glucose
+ bool success = await health.writeHealthData(10, HealthDataType.STEPS, now, now);
+ success = await health.writeHealthData(3.1, HealthDataType.BLOOD_GLUCOSE, now, now);
+
+ // get the number of steps for today
+ var midnight = DateTime(now.year, now.month, now.day);
+ int? steps = await health.getTotalStepsInInterval(midnight, now);
+```
-### Health data
+### Health Data
A `HealthData` object contains the following data fields:
diff --git a/packages/health/example/.flutter-plugins-dependencies b/packages/health/example/.flutter-plugins-dependencies
index 37ecaab6b..ad5314445 100644
--- a/packages/health/example/.flutter-plugins-dependencies
+++ b/packages/health/example/.flutter-plugins-dependencies
@@ -1 +1 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus-3.1.1\\\\","dependencies":[]},{"name":"health","path":"D:\\\\SheepApps\\\\Flutter\\\\flutter-plugins\\\\packages\\\\health\\\\","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus-3.1.1\\\\","dependencies":[]},{"name":"health","path":"D:\\\\SheepApps\\\\Flutter\\\\flutter-plugins\\\\packages\\\\health\\\\","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus_macos-2.2.0\\\\","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"D:\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\device_info_plus_web-2.1.0\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-12-03 11:04:00.730455","version":"2.5.3"}
\ No newline at end of file
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_macos-2.2.0/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_web-2.1.0/","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-12-29 20:33:27.370175","version":"2.5.3"}
\ No newline at end of file
diff --git a/packages/health/example/ios/Flutter/flutter_export_environment.sh b/packages/health/example/ios/Flutter/flutter_export_environment.sh
index c00f81a61..671cbf9db 100755
--- a/packages/health/example/ios/Flutter/flutter_export_environment.sh
+++ b/packages/health/example/ios/Flutter/flutter_export_environment.sh
@@ -3,11 +3,12 @@
export "FLUTTER_ROOT=/Users/bardram/dev/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/bardram/dev/flutter-plugins/packages/health/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
-export "FLUTTER_TARGET=lib/main.dart"
+export "FLUTTER_TARGET=/Users/bardram/dev/flutter-plugins/packages/health/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
-export "FLUTTER_BUILD_NAME=1.0.0"
-export "FLUTTER_BUILD_NUMBER=1"
+export "FLUTTER_BUILD_NAME=3.4.0"
+export "FLUTTER_BUILD_NUMBER=3.4.0"
+export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
export "DART_OBFUSCATION=false"
-export "TRACK_WIDGET_CREATION=false"
+export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
-export "PACKAGE_CONFIG=.packages"
+export "PACKAGE_CONFIG=/Users/bardram/dev/flutter-plugins/packages/health/example/.dart_tool/package_config.json"
diff --git a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj
index cfc875d1f..b6fb61701 100644
--- a/packages/health/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/health/example/ios/Runner.xcodeproj/project.pbxproj
@@ -50,6 +50,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ D6912A28277CD12C0012EA21 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -107,6 +108,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ D6912A28277CD12C0012EA21 /* RunnerDebug.entitlements */,
82F0E64E2375BDAE0022096E /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
@@ -174,7 +176,7 @@
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
- DevelopmentTeam = 59TCTNUBMQ;
+ DevelopmentTeam = 8TB3T6MAZG;
LastSwiftMigration = 0910;
ProvisioningStyle = Automatic;
SystemCapabilities = {
@@ -514,11 +516,11 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
+ CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 59TCTNUBMQ;
+ DEVELOPMENT_TEAM = 8TB3T6MAZG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -531,7 +533,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.example;
+ PRODUCT_BUNDLE_IDENTIFIER = "carp-health-example";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
diff --git a/packages/health/example/ios/Runner/RunnerDebug.entitlements b/packages/health/example/ios/Runner/RunnerDebug.entitlements
new file mode 100644
index 000000000..2ab14a262
--- /dev/null
+++ b/packages/health/example/ios/Runner/RunnerDebug.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.healthkit
+
+ com.apple.developer.healthkit.access
+
+
+
diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart
index 5b57fef8a..b14045c1a 100644
--- a/packages/health/example/lib/main.dart
+++ b/packages/health/example/lib/main.dart
@@ -4,11 +4,11 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:health/health.dart';
-void main() => runApp(MyApp());
+void main() => runApp(HealthApp());
-class MyApp extends StatefulWidget {
+class HealthApp extends StatefulWidget {
@override
- _MyAppState createState() => _MyAppState();
+ _HealthAppState createState() => _HealthAppState();
}
enum AppState {
@@ -19,43 +19,106 @@ enum AppState {
AUTH_NOT_GRANTED,
DATA_ADDED,
DATA_NOT_ADDED,
+ STEPS_READY,
}
-class _MyAppState extends State {
+class _HealthAppState extends State {
List _healthDataList = [];
AppState _state = AppState.DATA_NOT_FETCHED;
int _nofSteps = 10;
double _mgdl = 10.0;
- @override
- void initState() {
- super.initState();
+ // create a HealthFactory for use in the app
+ HealthFactory health = HealthFactory();
+
+ /// Fetch data points from the health plugin and show them in the app.
+ Future fetchData() async {
+ setState(() => _state = AppState.FETCHING_DATA);
+
+ // define the types to get
+ final types = [
+ HealthDataType.STEPS,
+ HealthDataType.WEIGHT,
+ HealthDataType.HEIGHT,
+ HealthDataType.BLOOD_GLUCOSE,
+ // Uncomment this line on iOS - only available on iOS
+ // HealthDataType.DISTANCE_WALKING_RUNNING,
+ ];
+
+ // with coresponsing permissions
+ final permissions = [
+ HealthDataAccess.READ,
+ HealthDataAccess.READ,
+ HealthDataAccess.READ,
+ HealthDataAccess.READ,
+ ];
+
+ // get data within the last 24 hours
+ final now = DateTime.now();
+ final yesterday = now.subtract(Duration(days: 1));
+
+ // requesting access to the data types before reading them
+ // note that strictly speaking, the [permissions] are not
+ // needed, since we only want READ access.
+ bool requested =
+ await health.requestAuthorization(types, permissions: permissions);
+
+ if (requested) {
+ try {
+ // fetch health data
+ List healthData =
+ await health.getHealthDataFromTypes(yesterday, now, types);
+
+ // save all the new data points (only the first 100)
+ _healthDataList.addAll((healthData.length < 100)
+ ? healthData
+ : healthData.sublist(0, 100));
+ } catch (error) {
+ print("Exception in getHealthDataFromTypes: $error");
+ }
+
+ // filter out duplicates
+ _healthDataList = HealthFactory.removeDuplicates(_healthDataList);
+
+ // print the results
+ _healthDataList.forEach((x) => print(x));
+
+ // update the UI to display the results
+ setState(() {
+ _state =
+ _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY;
+ });
+ } else {
+ print("Authorization not granted");
+ setState(() => _state = AppState.DATA_NOT_FETCHED);
+ }
}
+ /// Add some random health data.
Future addData() async {
- HealthFactory health = HealthFactory();
-
- final time = DateTime.now();
- final ago = time.add(Duration(minutes: -5));
+ final now = DateTime.now();
+ final earlier = now.subtract(Duration(minutes: 5));
_nofSteps = Random().nextInt(10);
final types = [HealthDataType.STEPS, HealthDataType.BLOOD_GLUCOSE];
final rights = [HealthDataAccess.WRITE, HealthDataAccess.WRITE];
- final permissions = [HealthDataAccess.READ_WRITE, HealthDataAccess.READ_WRITE];
+ final permissions = [
+ HealthDataAccess.READ_WRITE,
+ HealthDataAccess.READ_WRITE
+ ];
bool? hasPermissions =
await HealthFactory.hasPermissions(types, permissions: rights);
if (hasPermissions == false) {
await health.requestAuthorization(types, permissions: permissions);
}
- //_nofSteps = Random().nextInt(10);
_mgdl = Random().nextInt(10) * 1.0;
bool success = await health.writeHealthData(
- _nofSteps.toDouble(), HealthDataType.STEPS, ago, time);
+ _nofSteps.toDouble(), HealthDataType.STEPS, earlier, now);
if (success) {
success = await health.writeHealthData(
- _mgdl, HealthDataType.BLOOD_GLUCOSE, time, time);
+ _mgdl, HealthDataType.BLOOD_GLUCOSE, now, now);
}
setState(() {
@@ -63,64 +126,28 @@ class _MyAppState extends State {
});
}
- /// Fetch data from the healt plugin and print it
- Future fetchData() async {
- // get everything from midnight until now
- DateTime startDate = DateTime(2020, 11, 07, 0, 0, 0);
- DateTime endDate = DateTime(2025, 11, 07, 23, 59, 59);
-
- HealthFactory health = HealthFactory();
-
- // define the types to get
- List types = [
- HealthDataType.STEPS,
- HealthDataType.WEIGHT,
- HealthDataType.HEIGHT,
- HealthDataType.BLOOD_GLUCOSE,
- // Uncomment this line on iOS. This type is supported ONLY on Android!
- // HealthDataType.DISTANCE_WALKING_RUNNING,
- ];
-
- List permissions = [
- HealthDataAccess.READ_WRITE,
- HealthDataAccess.READ,
- HealthDataAccess.READ,
- HealthDataAccess.READ_WRITE,
- ];
+ /// Fetch steps from the health plugin and show them in the app.
+ Future fetchStepData() async {
+ int? steps;
- setState(() => _state = AppState.FETCHING_DATA);
+ // get steps for today (i.e., since midnight)
+ final now = DateTime.now();
+ final midnight = DateTime(now.year, now.month, now.day);
- // you MUST request access to the data types before reading them
- bool requested = await health.requestAuthorization(types, permissions: permissions);
- int steps = 0;
+ bool requested = await health.requestAuthorization([HealthDataType.STEPS]);
if (requested) {
try {
- // fetch new data
- List healthData =
- await health.getHealthDataFromTypes(startDate, endDate, types);
-
- // save all the new data points
- _healthDataList.addAll(healthData);
- } catch (e) {
- print("Caught exception in getHealthDataFromTypes: $e");
+ steps = await health.getTotalStepsInInterval(midnight, now);
+ } catch (error) {
+ print("Caught exception in getTotalStepsInInterval: $error");
}
- // filter out duplicates
- _healthDataList = HealthFactory.removeDuplicates(_healthDataList);
-
- // print the results
- _healthDataList.forEach((x) {
- print("Data point: $x");
- steps += x.value.round();
- });
-
- print("Steps: $steps");
+ print('Total number of steps: $steps');
- // update the UI to display the results
setState(() {
- _state =
- _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY;
+ _nofSteps = (steps == null) ? 0 : steps;
+ _state = (steps == null) ? AppState.NO_DATA : AppState.STEPS_READY;
});
} else {
print("Authorization not granted");
@@ -163,22 +190,27 @@ class _MyAppState extends State {
return Column(
children: [
Text('Press the download button to fetch data.'),
- Text('Press the plus button to insert some random data.')
+ Text('Press the plus button to insert some random data.'),
+ Text('Press the walking button to get total step count.'),
],
mainAxisAlignment: MainAxisAlignment.center,
);
}
Widget _authorizationNotGranted() {
- return Text('''Authorization not given.
- For Android please check your OAUTH2 client ID is correct in Google Developer Console.
- For iOS check your permissions in Apple Health.''');
+ return Text('Authorization not given. '
+ 'For Android please check your OAUTH2 client ID is correct in Google Developer Console. '
+ 'For iOS check your permissions in Apple Health.');
}
Widget _dataAdded() {
return Text('$_nofSteps steps and $_mgdl mgdl are inserted successfully!');
}
+ Widget _stepsFetched() {
+ return Text('Total number of steps: $_nofSteps');
+ }
+
Widget _dataNotAdded() {
return Text('Failed to add data');
}
@@ -194,6 +226,8 @@ class _MyAppState extends State {
return _authorizationNotGranted();
else if (_state == AppState.DATA_ADDED)
return _dataAdded();
+ else if (_state == AppState.STEPS_READY)
+ return _stepsFetched();
else if (_state == AppState.DATA_NOT_ADDED) return _dataNotAdded();
return _contentNotFetched();
@@ -204,7 +238,7 @@ class _MyAppState extends State {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
- title: const Text('Plugin example app'),
+ title: const Text('Health Example'),
actions: [
IconButton(
icon: Icon(Icons.file_download),
@@ -213,10 +247,17 @@ class _MyAppState extends State {
},
),
IconButton(
- onPressed: () {
- addData();
- },
- icon: Icon(Icons.add))
+ onPressed: () {
+ addData();
+ },
+ icon: Icon(Icons.add),
+ ),
+ IconButton(
+ onPressed: () {
+ fetchStepData();
+ },
+ icon: Icon(Icons.nordic_walking),
+ )
],
),
body: Center(
diff --git a/packages/health/example/pubspec.yaml b/packages/health/example/pubspec.yaml
index fe5d9380a..5005c1c69 100644
--- a/packages/health/example/pubspec.yaml
+++ b/packages/health/example/pubspec.yaml
@@ -1,6 +1,7 @@
name: health_example
description: Demonstrates how to use the health plugin.
publish_to: 'none'
+version: 3.4.0
environment:
sdk: ">=2.12.0-0 <3.0.0"
diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift
index d406d7a1f..ffab6192e 100644
--- a/packages/health/ios/Classes/SwiftHealthPlugin.swift
+++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift
@@ -314,7 +314,6 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
if let quantity = queryResult.sumQuantity() {
let unit = HKUnit.count()
steps = quantity.doubleValue(for: unit)
- print("Amount of steps: \(steps)")
}
let totalSteps = Int(steps)
diff --git a/packages/health/lib/src/data_types.dart b/packages/health/lib/src/data_types.dart
index 6a50386b9..4eb484a44 100644
--- a/packages/health/lib/src/data_types.dart
+++ b/packages/health/lib/src/data_types.dart
@@ -48,6 +48,7 @@ enum HealthDataAccess {
WRITE,
READ_WRITE,
}
+
/// List of data types available on iOS
const List _dataTypeKeysIOS = [
HealthDataType.ACTIVE_ENERGY_BURNED,
diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart
index 974db32c5..fef88fd36 100644
--- a/packages/health/lib/src/health_factory.dart
+++ b/packages/health/lib/src/health_factory.dart
@@ -1,6 +1,6 @@
part of health;
-/// Main class for the Plugin
+/// Main class for the Plugin.
class HealthFactory {
static const MethodChannel _channel = MethodChannel('flutter_health');
String? _deviceId;
@@ -17,26 +17,26 @@ class HealthFactory {
/// Determines if the data types have been granted with the specified access rights.
///
- /// Returns:
- ///
+ /// Returns:
+ ///
/// * true - if all of the data types have been granted with the specfied access rights.
/// * false - if any of the data types has not been granted with the specified access right(s)
/// * null - if it can not be determined if the data types have been granted with the specified access right(s).
///
/// Parameters:
- ///
+ ///
/// * [types] - List of [HealthDataType] whose permissions are to be checked.
- /// * [permissions] - Optional.
+ /// * [permissions] - Optional.
/// + If unspecified, this method checks if each HealthDataType in [types] has been granted READ access.
- /// + If specified, this method checks if each [HealthDataType] in [types] has been granted with the access specified in its
+ /// + If specified, this method checks if each [HealthDataType] in [types] has been granted with the access specified in its
/// corresponding entry in this list. The length of this list must be equal to that of [types].
- ///
+ ///
/// Caveat:
- ///
+ ///
/// As Apple HealthKit will not disclose if READ access has been granted for a data type due to privacy concern,
/// this method can only return null to represent an undertermined status, if it is called on iOS
- /// with a READ or READ_WRITE access.
- ///
+ /// with a READ or READ_WRITE access.
+ ///
/// On Android, this function returns true or false, depending on whether the specified access right has been granted.
static Future hasPermissions(List types,
{List? permissions}) async {
@@ -51,8 +51,7 @@ class HealthFactory {
: permissions.map((permission) => permission.index).toList();
/// On Android, if BMI is requested, then also ask for weight and height
- if (_platformType == PlatformType.ANDROID)
- _handleBMI(mTypes, mPermissions);
+ if (_platformType == PlatformType.ANDROID) _handleBMI(mTypes, mPermissions);
return await _channel.invokeMethod('hasPermissions', {
"types": mTypes.map((type) => _enumToString(type)).toList(),
@@ -71,24 +70,24 @@ class HealthFactory {
});
}
- /// iOS isn't supported by HealthKit, method does nothing.
+ /// Revoke permissions obtained earlier.
+ ///
+ /// Not supported on iOS and method does nothing.
static Future revokePermissions() async {
return await _channel.invokeMethod('revokePermissions');
}
+ /// Requests permissions to access data types in Apple Health or Google Fit.
///
- /// Requests permissions to access data types in the HealthKit or Google Fit store.
+ /// Returns true if successful, false otherwise
///
- /// Returns a Future of true if successful, a Future of false otherwise
- ///
- /// Parameters
+ /// Parameters:
///
/// * [types] - a list of [HealthDataType] which the permissions are requested for.
- /// * [permissions] - Optional.
+ /// * [permissions] - Optional.
/// + If unspecified, each [HealthDataType] in [types] is requested for READ [HealthDataAccess].
- /// + If specified, each [HealthDataAccess] in this list is requested for its corresponding indexed
+ /// + If specified, each [HealthDataAccess] in this list is requested for its corresponding indexed
/// entry in [types]. In addition, the length of this list must be equal to that of [types].
- ///
Future requestAuthorization(List types,
{List? permissions}) async {
if (permissions != null && permissions.length != types.length) {
@@ -102,9 +101,8 @@ class HealthFactory {
growable: true)
: permissions.map((permission) => permission.index).toList();
- /// On Android, if BMI is requested, then also ask for weight and height
- if (_platformType == PlatformType.ANDROID)
- _handleBMI(mTypes, mPermissions);
+ // on Android, if BMI is requested, then also ask for weight and height
+ if (_platformType == PlatformType.ANDROID) _handleBMI(mTypes, mPermissions);
List keys = mTypes.map((e) => _enumToString(e)).toList();
final bool isAuthorized = await _channel.invokeMethod(
@@ -112,8 +110,7 @@ class HealthFactory {
return isAuthorized;
}
- static void _handleBMI(
- List mTypes, List mPermissions) {
+ static void _handleBMI(List mTypes, List mPermissions) {
final index = mTypes.indexOf(HealthDataType.BODY_MASS_INDEX);
if (index != -1 && _platformType == PlatformType.ANDROID) {
@@ -159,13 +156,11 @@ class HealthFactory {
return bmiHealthPoints;
}
+ /// Saves health data into Apple Health or Google Fit.
///
- /// Saves health data into the HealthKit or Google Fit store.
- ///
- /// Returns a Future of true if successful, a Future of false otherwise
+ /// Returns true if successful, false otherwise.
///
/// Parameters:
- ///
/// * [value] - the health data's value in double
/// * [type] - the value's HealthDataType
/// * [startTime] - the start time when this [value] is measured.
@@ -174,8 +169,12 @@ class HealthFactory {
/// + It must be equal to or later than [startTime].
/// + Simply set [endTime] equal to [startTime] if the [value] is measured only at a specific point in time.
///
- Future writeHealthData(double value, HealthDataType type,
- DateTime startTime, DateTime endTime) async {
+ Future writeHealthData(
+ double value,
+ HealthDataType type,
+ DateTime startTime,
+ DateTime endTime,
+ ) async {
if (startTime.isAfter(endTime))
throw ArgumentError("startTime must be equal or earlier than endTime");
Map args = {
@@ -188,8 +187,12 @@ class HealthFactory {
return success ?? false;
}
+ /// Fetch a list of health data points based on [types].
Future> getHealthDataFromTypes(
- DateTime startDate, DateTime endDate, List types) async {
+ DateTime startDate,
+ DateTime endDate,
+ List types,
+ ) async {
List dataPoints = [];
for (var type in types) {
@@ -224,7 +227,6 @@ class HealthFactory {
/// The main function for fetching health data
Future> _dataQuery(
DateTime startDate, DateTime endDate, HealthDataType dataType) async {
- // Set parameters for method channel request
final args = {
'dataTypeKey': _enumToString(dataType),
'startDate': startDate.millisecondsSinceEpoch,
@@ -278,11 +280,14 @@ class HealthFactory {
return unique;
}
+ /// Get the total numbner of steps within a specific time period.
+ /// Returns null if not successful.
+ ///
+ /// Is a fix according to https://stackoverflow.com/questions/29414386/step-count-retrieved-through-google-fit-api-does-not-match-step-count-displayed/29415091#29415091
Future getTotalStepsInInterval(
DateTime startDate,
DateTime endDate,
) async {
- // Set parameters for method channel request
final args = {
'startDate': startDate.millisecondsSinceEpoch,
'endDate': endDate.millisecondsSinceEpoch
diff --git a/packages/health/pubspec.yaml b/packages/health/pubspec.yaml
index 5ee963dce..0b85d3df6 100644
--- a/packages/health/pubspec.yaml
+++ b/packages/health/pubspec.yaml
@@ -1,6 +1,6 @@
name: health
description: Wrapper for the iOS HealthKit and Android GoogleFit services.
-version: 3.3.1
+version: 3.4.0
homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages/health
environment:
From 4d446ae083c831336b1c3d33bab22dc8ac3af1ad Mon Sep 17 00:00:00 2001
From: bardram
Date: Wed, 29 Dec 2021 21:33:35 +0100
Subject: [PATCH 11/30] health v 3.4.0 published
---
.gitignore | 1 +
packages/health/example/.flutter-plugins-dependencies | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 86e4f2f17..30fa7670d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,3 +123,4 @@ packages/audio_streamer/example/.flutter-plugins-dependencies
packages/health/example/.flutter-plugins-dependencies
*.sh
packages/health/example/.flutter-plugins-dependencies
+packages/health/example/.flutter-plugins-dependencies
diff --git a/packages/health/example/.flutter-plugins-dependencies b/packages/health/example/.flutter-plugins-dependencies
index ad5314445..4768c46ee 100644
--- a/packages/health/example/.flutter-plugins-dependencies
+++ b/packages/health/example/.flutter-plugins-dependencies
@@ -1 +1 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_macos-2.2.0/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_web-2.1.0/","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-12-29 20:33:27.370175","version":"2.5.3"}
\ No newline at end of file
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"android":[{"name":"device_info_plus","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus-3.1.1/","dependencies":[]},{"name":"health","path":"/Users/bardram/dev/flutter-plugins/packages/health/","dependencies":["device_info_plus"]}],"macos":[{"name":"device_info_plus_macos","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_macos-2.2.0/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"device_info_plus_web","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/device_info_plus_web-2.1.0/","dependencies":[]}]},"dependencyGraph":[{"name":"device_info_plus","dependencies":["device_info_plus_macos","device_info_plus_web"]},{"name":"device_info_plus_macos","dependencies":[]},{"name":"device_info_plus_web","dependencies":[]},{"name":"health","dependencies":["device_info_plus"]}],"date_created":"2021-12-29 21:18:27.488582","version":"2.5.3"}
\ No newline at end of file
From 2bc3cf09b3519b564319ad728ac045d21c3ad9b6 Mon Sep 17 00:00:00 2001
From: bardram
Date: Thu, 30 Dec 2021 15:11:28 +0100
Subject: [PATCH 12/30] activity_recognition published v. 4.1.0
---
.gitignore | 1 +
.../activity_recognition_flutter/CHANGELOG.md | 5 ++
.../ActivityRecognitionFlutterPlugin.java | 17 ++--
.../example/.flutter-plugins-dependencies | 1 -
.../ios/Flutter/flutter_export_environment.sh | 14 ---
.../ios/Runner.xcodeproj/project.pbxproj | 13 +--
.../xcshareddata/WorkspaceSettings.xcsettings | 2 +-
.../xcshareddata/xcschemes/Runner.xcscheme | 10 +--
.../example/ios/Runner/Info.plist | 2 -
.../example/lib/main.dart | 86 +++++++++++++------
.../example/pubspec.yaml | 1 +
.../example/test/widget_test.dart | 2 +-
.../lib/activity_recognition_flutter.dart | 2 +-
.../activity_recognition_flutter/pubspec.yaml | 2 +-
14 files changed, 93 insertions(+), 65 deletions(-)
delete mode 100644 packages/activity_recognition_flutter/example/.flutter-plugins-dependencies
delete mode 100755 packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh
diff --git a/.gitignore b/.gitignore
index 30fa7670d..0de9e2302 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,3 +124,4 @@ packages/health/example/.flutter-plugins-dependencies
*.sh
packages/health/example/.flutter-plugins-dependencies
packages/health/example/.flutter-plugins-dependencies
+packages/activity_recognition_flutter/example/.flutter-plugins-dependencies
diff --git a/packages/activity_recognition_flutter/CHANGELOG.md b/packages/activity_recognition_flutter/CHANGELOG.md
index 37e1eef49..3bbbed3d4 100644
--- a/packages/activity_recognition_flutter/CHANGELOG.md
+++ b/packages/activity_recognition_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 4.1.0
+* [PR #474](https://github.com/cph-cachet/flutter-plugins/pull/474) - Android 12 intent-flag
+* then name of the stream has been changed from `startStream` to `activityStream`
+* cleanup in example app
+
## 4.0.5+1
* [PR #408](https://github.com/cph-cachet/flutter-plugins/pull/408)
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
index 9f02e5dcb..6fb0c41d6 100644
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
+++ b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
@@ -45,8 +45,10 @@ public class ActivityRecognitionFlutterPlugin implements FlutterPlugin, EventCha
private void startActivityTracking() {
// Start the service
Intent intent = new Intent(androidActivity, ActivityRecognizedBroadcastReceiver.class);
+
+ Log.d(TAG, "SDK = " + Build.VERSION.SDK_INT);
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
- if (Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 31) {
flags |= PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = PendingIntent.getBroadcast(androidActivity, 0, intent, flags);
@@ -59,13 +61,13 @@ private void startActivityTracking() {
task.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(Void e) {
- Log.d(TAG, "ActivityRecognition: onSuccess");
+ Log.d(TAG, "Successfully registered ActivityRecognition listener.");
}
});
task.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
- Log.d(TAG, "ActivityRecognition: onFailure");
+ Log.d(TAG, "Failed to registered ActivityRecognition listener.");
}
});
}
@@ -86,12 +88,11 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
HashMap args = (HashMap) arguments;
- Log.d(TAG, "args: " + args);
boolean fg = (boolean) args.get("foreground");
if(fg) {
startForegroundService();
}
- Log.d(TAG, "foreground: " + fg);
+ Log.d(TAG, "Foreground moce: " + fg);
eventSink = events;
@@ -136,7 +137,7 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
SharedPreferences prefs = androidContext.getSharedPreferences(ACTIVITY_RECOGNITION, Context.MODE_PRIVATE);
prefs.registerOnSharedPreferenceChangeListener(this);
- Log.d(TAG, "onAttachedToActivity");
+ // Log.d(TAG, "onAttachedToActivity");
}
@Override
@@ -165,9 +166,9 @@ public void onDetachedFromActivity() {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
String result = sharedPreferences
.getString(DETECTED_ACTIVITY, "error");
- Log.d("onSharedPreferenceChange", result);
+ // Log.d("onSharedPreferenceChange", result);
if (key!= null && key.equals(DETECTED_ACTIVITY)) {
- Log.d(TAG, "Detected activity: " + result);
+ // Log.d(TAG, "Detected activity: " + result);
eventSink.success(result);
}
}
diff --git a/packages/activity_recognition_flutter/example/.flutter-plugins-dependencies b/packages/activity_recognition_flutter/example/.flutter-plugins-dependencies
deleted file mode 100644
index f70808a88..000000000
--- a/packages/activity_recognition_flutter/example/.flutter-plugins-dependencies
+++ /dev/null
@@ -1 +0,0 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"activity_recognition_flutter","path":"/Users/bardram/dev/flutter-plugins/packages/activity_recognition_flutter/","dependencies":[]},{"name":"permission_handler","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/permission_handler-7.1.0/","dependencies":[]}],"android":[{"name":"activity_recognition_flutter","path":"/Users/bardram/dev/flutter-plugins/packages/activity_recognition_flutter/","dependencies":[]},{"name":"permission_handler","path":"/Users/bardram/.pub-cache/hosted/pub.dartlang.org/permission_handler-7.1.0/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"activity_recognition_flutter","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2021-10-06 09:54:26.345214","version":"2.2.3"}
\ No newline at end of file
diff --git a/packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh b/packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh
deleted file mode 100755
index 934c5797a..000000000
--- a/packages/activity_recognition_flutter/example/ios/Flutter/flutter_export_environment.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-# This is a generated file; do not edit or check into version control.
-export "FLUTTER_ROOT=/Users/bardram/dev/flutter"
-export "FLUTTER_APPLICATION_PATH=/Users/bardram/dev/flutter-plugins/packages/activity_recognition_flutter/example"
-export "COCOAPODS_PARALLEL_CODE_SIGN=true"
-export "FLUTTER_TARGET=lib/main.dart"
-export "FLUTTER_BUILD_DIR=build"
-export "SYMROOT=${SOURCE_ROOT}/../build/ios"
-export "FLUTTER_BUILD_NAME=1.0.0"
-export "FLUTTER_BUILD_NUMBER=1"
-export "DART_OBFUSCATION=false"
-export "TRACK_WIDGET_CREATION=false"
-export "TREE_SHAKE_ICONS=false"
-export "PACKAGE_CONFIG=.packages"
diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj
index 12033be9e..459ed6684 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj
@@ -155,7 +155,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1020;
+ LastUpgradeCheck = 1320;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -321,6 +321,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -339,7 +340,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -400,6 +401,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -424,7 +426,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -455,6 +457,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -473,7 +476,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -491,7 +494,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = H33599VJ27;
+ DEVELOPMENT_TEAM = 8TB3T6MAZG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
index 6b30c7459..530b83358 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -3,7 +3,7 @@
BuildSystemType
- Original
+ Latest
PreviewsEnabled
diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140cfd..6dd6010f8 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
-
-
-
-
+
+
-
-
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
diff --git a/packages/activity_recognition_flutter/example/lib/main.dart b/packages/activity_recognition_flutter/example/lib/main.dart
index 9050184c3..d760cee6c 100644
--- a/packages/activity_recognition_flutter/example/lib/main.dart
+++ b/packages/activity_recognition_flutter/example/lib/main.dart
@@ -1,19 +1,19 @@
+import 'dart:async';
import 'dart:io';
import 'package:activity_recognition_flutter/activity_recognition_flutter.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
-void main() => runApp(new MyApp());
+void main() => runApp(new ActivityRecognitionApp());
-class MyApp extends StatefulWidget {
+class ActivityRecognitionApp extends StatefulWidget {
@override
- _MyAppState createState() => new _MyAppState();
+ _ActivityRecognitionAppState createState() => _ActivityRecognitionAppState();
}
-class _MyAppState extends State {
- late Stream activityStream;
- ActivityEvent latestActivity = ActivityEvent.empty();
+class _ActivityRecognitionAppState extends State {
+ StreamSubscription? activityStreamSubscription;
List _events = [];
ActivityRecognition activityRecognition = ActivityRecognition.instance;
@@ -21,55 +21,93 @@ class _MyAppState extends State {
void initState() {
super.initState();
_init();
+ _events.add(ActivityEvent.empty());
+ }
+
+ @override
+ void dispose() {
+ activityStreamSubscription?.cancel();
+ super.dispose();
}
void _init() async {
- /// Android requires explicitly asking permission
+ // Android requires explicitly asking permission
if (Platform.isAndroid) {
if (await Permission.activityRecognition.request().isGranted) {
_startTracking();
}
}
- /// iOS does not
+ // iOS does not
else {
_startTracking();
}
}
void _startTracking() {
- activityStream =
- activityRecognition.startStream(runForegroundService: true);
- activityStream.listen(onData);
+ activityStreamSubscription = activityRecognition
+ .activityStream(runForegroundService: true)
+ .listen(onData, onError: onError);
}
void onData(ActivityEvent activityEvent) {
- print(activityEvent.toString());
+ print('ACTIVITY - $activityEvent');
setState(() {
_events.add(activityEvent);
- latestActivity = activityEvent;
});
}
+ void onError(Object error) {
+ print('ERROR - $error');
+ }
+
@override
Widget build(BuildContext context) {
- return new MaterialApp(
- home: new Scaffold(
- appBar: new AppBar(
- title: const Text('Activity Recognition Demo'),
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(
+ title: const Text('Activity Recognition'),
),
- body: new Center(
- child: new ListView.builder(
+ body: Center(
+ child: ListView.builder(
itemCount: _events.length,
reverse: true,
- itemBuilder: (BuildContext context, int idx) {
- final entry = _events[idx];
+ itemBuilder: (_, int idx) {
+ final activity = _events[idx];
return ListTile(
- leading:
- Text(entry.timeStamp.toString().substring(0, 19)),
- trailing: Text(entry.type.toString().split('.').last));
+ leading: _activityIcon(activity.type),
+ title: Text(
+ '${activity.type.toString().split('.').last} (${activity.confidence}%)'),
+ trailing: Text(activity.timeStamp
+ .toString()
+ .split(' ')
+ .last
+ .split('.')
+ .first),
+ );
})),
),
);
}
+
+ Icon _activityIcon(ActivityType type) {
+ switch (type) {
+ case ActivityType.WALKING:
+ return Icon(Icons.directions_walk);
+ case ActivityType.IN_VEHICLE:
+ return Icon(Icons.car_rental);
+ case ActivityType.ON_BICYCLE:
+ return Icon(Icons.pedal_bike);
+ case ActivityType.ON_FOOT:
+ return Icon(Icons.directions_walk);
+ case ActivityType.RUNNING:
+ return Icon(Icons.run_circle);
+ case ActivityType.STILL:
+ return Icon(Icons.cancel_outlined);
+ case ActivityType.TILTING:
+ return Icon(Icons.redo);
+ default:
+ return Icon(Icons.device_unknown);
+ }
+ }
}
diff --git a/packages/activity_recognition_flutter/example/pubspec.yaml b/packages/activity_recognition_flutter/example/pubspec.yaml
index 46219a48b..479b39522 100644
--- a/packages/activity_recognition_flutter/example/pubspec.yaml
+++ b/packages/activity_recognition_flutter/example/pubspec.yaml
@@ -1,4 +1,5 @@
name: activity_recognition_flutter_example
+version: 4.1.0
description: Demonstrates how to use the activity_recognition_flutter plugin.
# The following line prevents the package from being accidentally published to
diff --git a/packages/activity_recognition_flutter/example/test/widget_test.dart b/packages/activity_recognition_flutter/example/test/widget_test.dart
index 90c68b250..6e03e9854 100644
--- a/packages/activity_recognition_flutter/example/test/widget_test.dart
+++ b/packages/activity_recognition_flutter/example/test/widget_test.dart
@@ -13,7 +13,7 @@ import 'package:activity_recognition_flutter_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
- await tester.pumpWidget(MyApp());
+ await tester.pumpWidget(ActivityRecognitionApp());
// Verify that platform version is retrieved.
expect(
diff --git a/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart b/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart
index 2b7f86f69..a2c49088d 100644
--- a/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart
+++ b/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart
@@ -27,7 +27,7 @@ class ActivityRecognition {
/// By default the foreground service is enabled, which allows the
/// updates to be streamed while the app runs in the background.
/// The programmer can choose to not enable to foreground service.
- Stream startStream({bool runForegroundService = true}) {
+ Stream activityStream({bool runForegroundService = true}) {
if (_stream == null) {
_stream = _eventChannel
.receiveBroadcastStream({"foreground": runForegroundService}).map(
diff --git a/packages/activity_recognition_flutter/pubspec.yaml b/packages/activity_recognition_flutter/pubspec.yaml
index e10533abb..67b4f1feb 100644
--- a/packages/activity_recognition_flutter/pubspec.yaml
+++ b/packages/activity_recognition_flutter/pubspec.yaml
@@ -1,6 +1,6 @@
name: activity_recognition_flutter
description: Activity recognition plugin for Android and iOS. Provides event-based information about activities detected by the phone.
-version: 4.0.5+1
+version: 4.1.0
homepage: https://github.com/cph-cachet/flutter-plugins/tree/master/packages
environment:
From e40e223fdd67628dfb7322ee273b688628e758c1 Mon Sep 17 00:00:00 2001
From: bardram
Date: Fri, 31 Dec 2021 15:25:04 +0100
Subject: [PATCH 13/30] small typo fix
---
.../ActivityRecognitionFlutterPlugin.java | 2 +-
.../example/lib/main.dart | 35 ++++++++++---------
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
index 6fb0c41d6..27198b955 100644
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
+++ b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
@@ -92,7 +92,7 @@ public void onListen(Object arguments, EventChannel.EventSink events) {
if(fg) {
startForegroundService();
}
- Log.d(TAG, "Foreground moce: " + fg);
+ Log.d(TAG, "Foreground mode: " + fg);
eventSink = events;
diff --git a/packages/activity_recognition_flutter/example/lib/main.dart b/packages/activity_recognition_flutter/example/lib/main.dart
index d760cee6c..62e553122 100644
--- a/packages/activity_recognition_flutter/example/lib/main.dart
+++ b/packages/activity_recognition_flutter/example/lib/main.dart
@@ -69,23 +69,24 @@ class _ActivityRecognitionAppState extends State {
title: const Text('Activity Recognition'),
),
body: Center(
- child: ListView.builder(
- itemCount: _events.length,
- reverse: true,
- itemBuilder: (_, int idx) {
- final activity = _events[idx];
- return ListTile(
- leading: _activityIcon(activity.type),
- title: Text(
- '${activity.type.toString().split('.').last} (${activity.confidence}%)'),
- trailing: Text(activity.timeStamp
- .toString()
- .split(' ')
- .last
- .split('.')
- .first),
- );
- })),
+ child: ListView.builder(
+ itemCount: _events.length,
+ reverse: true,
+ itemBuilder: (_, int idx) {
+ final activity = _events[idx];
+ return ListTile(
+ leading: _activityIcon(activity.type),
+ title: Text(
+ '${activity.type.toString().split('.').last} (${activity.confidence}%)'),
+ trailing: Text(activity.timeStamp
+ .toString()
+ .split(' ')
+ .last
+ .split('.')
+ .first),
+ );
+ }),
+ ),
),
);
}
From d7dd774c4dd929c4888ab1fc3670074eaf4cd36d Mon Sep 17 00:00:00 2001
From: bardram
Date: Tue, 4 Jan 2022 00:13:06 +0100
Subject: [PATCH 14/30] Update CHANGELOG.md
---
packages/activity_recognition_flutter/CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/activity_recognition_flutter/CHANGELOG.md b/packages/activity_recognition_flutter/CHANGELOG.md
index 3bbbed3d4..d145fdb2c 100644
--- a/packages/activity_recognition_flutter/CHANGELOG.md
+++ b/packages/activity_recognition_flutter/CHANGELOG.md
@@ -1,6 +1,6 @@
## 4.1.0
* [PR #474](https://github.com/cph-cachet/flutter-plugins/pull/474) - Android 12 intent-flag
-* then name of the stream has been changed from `startStream` to `activityStream`
+* the name of the stream has been changed from `startStream` to `activityStream`
* cleanup in example app
## 4.0.5+1
From 63efba69d567cc92880c4ce9fbfdd6b48a470bf7 Mon Sep 17 00:00:00 2001
From: Kin Mak
Date: Thu, 25 Nov 2021 18:45:46 +0800
Subject: [PATCH 15/30] moved parsing out of main thread
---
.../ios/Classes/SwiftHealthPlugin.swift | 69 ++++++++++---------
1 file changed, 36 insertions(+), 33 deletions(-)
diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift
index ffab6192e..07963ec9a 100644
--- a/packages/health/ios/Classes/SwiftHealthPlugin.swift
+++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift
@@ -224,19 +224,19 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
switch samplesOrNil {
case let (samples as [HKQuantitySample]) as Any:
+ let dictionaries = samples.map { sample -> NSDictionary in
+ let unit = self.unitLookUp(key: dataTypeKey)
+ return [
+ "uuid": "\(sample.uuid)",
+ "value": sample.quantity.doubleValue(for: unit),
+ "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000),
+ "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000),
+ "source_id": sample.sourceRevision.source.bundleIdentifier,
+ "source_name": sample.sourceRevision.source.name
+ ]
+ }
DispatchQueue.main.async {
- result(samples.map { sample -> NSDictionary in
- let unit = self.unitLookUp(key: dataTypeKey)
-
- return [
- "uuid": "\(sample.uuid)",
- "value": sample.quantity.doubleValue(for: unit),
- "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000),
- "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000),
- "source_id": sample.sourceRevision.source.bundleIdentifier,
- "source_name": sample.sourceRevision.source.name
- ]
- })
+ result(dictionaries)
}
case var (samplesCategory as [HKCategorySample]) as Any:
@@ -249,32 +249,35 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
if (dataTypeKey == self.SLEEP_ASLEEP) {
samplesCategory = samplesCategory.filter { $0.value == 1 }
}
-
+ let categories = samplesCategory.map { sample -> NSDictionary in
+ return [
+ "uuid": "\(sample.uuid)",
+ "value": sample.value,
+ "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000),
+ "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000),
+ "source_id": sample.sourceRevision.source.bundleIdentifier,
+ "source_name": sample.sourceRevision.source.name
+ ]
+ }
DispatchQueue.main.async {
- result(samplesCategory.map { sample -> NSDictionary in
- return [
- "uuid": "\(sample.uuid)",
- "value": sample.value,
- "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000),
- "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000),
- "source_id": sample.sourceRevision.source.bundleIdentifier,
- "source_name": sample.sourceRevision.source.name
- ]
- })
+ result(categories)
}
case let (samplesWorkout as [HKWorkout]) as Any:
+
+ let dictionaries = samplesWorkout.map { sample -> NSDictionary in
+ return [
+ "uuid": "\(sample.uuid)",
+ "value": Int(sample.duration),
+ "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000),
+ "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000),
+ "source_id": sample.sourceRevision.source.bundleIdentifier,
+ "source_name": sample.sourceRevision.source.name
+ ]
+ }
+
DispatchQueue.main.async {
- result(samplesWorkout.map { sample -> NSDictionary in
- return [
- "uuid": "\(sample.uuid)",
- "value": Int(sample.duration),
- "date_from": Int(sample.startDate.timeIntervalSince1970 * 1000),
- "date_to": Int(sample.endDate.timeIntervalSince1970 * 1000),
- "source_id": sample.sourceRevision.source.bundleIdentifier,
- "source_name": sample.sourceRevision.source.name
- ]
- })
+ result(dictionaries)
}
default:
From 33842eb81bd40e1f8fdedbb52a0c6c1f6515bfb0 Mon Sep 17 00:00:00 2001
From: Kin Mak
Date: Fri, 26 Nov 2021 17:40:36 +0800
Subject: [PATCH 16/30] using Isolate and moved parsing out of main thread
---
packages/health/lib/health.dart | 1 +
packages/health/lib/src/health_factory.dart | 65 ++++++++++++++-------
2 files changed, 45 insertions(+), 21 deletions(-)
diff --git a/packages/health/lib/health.dart b/packages/health/lib/health.dart
index ac6763d00..4fa0a9014 100644
--- a/packages/health/lib/health.dart
+++ b/packages/health/lib/health.dart
@@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:io' show Platform;
import 'package:device_info_plus/device_info_plus.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart
index fef88fd36..8119d50ca 100644
--- a/packages/health/lib/src/health_factory.dart
+++ b/packages/health/lib/src/health_factory.dart
@@ -233,34 +233,57 @@ class HealthFactory {
'endDate': endDate.millisecondsSinceEpoch
};
- final unit = _dataTypeToUnit[dataType]!;
-
final fetchedDataPoints = await _channel.invokeMethod('getData', args);
if (fetchedDataPoints != null) {
- return fetchedDataPoints.map((e) {
- final num value = e['value'];
- final DateTime from =
- DateTime.fromMillisecondsSinceEpoch(e['date_from']);
- final DateTime to = DateTime.fromMillisecondsSinceEpoch(e['date_to']);
- final String sourceId = e["source_id"];
- final String sourceName = e["source_name"];
- return HealthDataPoint(
- value,
- dataType,
- unit,
- from,
- to,
- _platformType,
- _deviceId!,
- sourceId,
- sourceName,
- );
- }).toList();
+ final mesg = {
+ "dataType": dataType,
+ "dataPoints": fetchedDataPoints,
+ "deviceId": _deviceId!,
+ };
+ const thresHold = 10;
+ // If the no. of data points are larger than the threshold,
+ // call the compute method to spawn an Isolate to do the parsing in a separate thread.
+ if (fetchedDataPoints.length > thresHold)
+ return compute(_parse, mesg);
+ return _parse(mesg);
} else {
return [];
}
}
+ static List _parse(dynamic message) {
+ final dataType = message["dataType"];
+ final dataPoints = message["dataPoints"];
+ final device = message["deviceId"];
+ final unit = _dataTypeToUnit[dataType]!;
+ final stopwatch = Stopwatch();
+ stopwatch.start();
+
+ final list = dataPoints.map((e) {
+ final num value = e['value'];
+ final DateTime from = DateTime.fromMillisecondsSinceEpoch(e['date_from']);
+ final DateTime to = DateTime.fromMillisecondsSinceEpoch(e['date_to']);
+ final String sourceId = e["source_id"];
+ final String sourceName = e["source_name"];
+ return HealthDataPoint(
+ value,
+ dataType,
+ unit,
+ from,
+ to,
+ _platformType,
+ device,
+ sourceId,
+ sourceName,
+ );
+ }).toList();
+
+ stopwatch.stop();
+
+ print('Nof items: ${list.length} time elapsed: ${stopwatch.elapsedMilliseconds} ms');
+ return list;
+ }
+
/// Given an array of [HealthDataPoint]s, this method will return the array
/// without any duplicates.
static List removeDuplicates(List points) {
From 35e547d7f226e7549316f883d9f8fa8900ec0402 Mon Sep 17 00:00:00 2001
From: Kin Mak
Date: Fri, 26 Nov 2021 17:52:14 +0800
Subject: [PATCH 17/30] fixed concurrent issues by tapping from thread pool and
using isolate
---
.../cachet/plugins/health/HealthPlugin.kt | 354 +++++++++---------
packages/health/lib/src/health_factory.dart | 22 +-
2 files changed, 189 insertions(+), 187 deletions(-)
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 d3a42f142..0ac228f9a 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
@@ -6,7 +6,6 @@ import com.google.android.gms.fitness.Fitness
import com.google.android.gms.fitness.FitnessOptions
import com.google.android.gms.fitness.request.DataReadRequest
import com.google.android.gms.fitness.result.DataReadResponse
-import com.google.android.gms.tasks.Tasks
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@@ -17,14 +16,18 @@ import android.os.Handler
import android.util.Log
import androidx.annotation.NonNull
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener
-import java.util.concurrent.TimeUnit
-import java.util.Date
-import kotlin.concurrent.thread
import com.google.android.gms.fitness.data.*
import com.google.android.gms.fitness.request.SessionReadRequest
+import com.google.android.gms.fitness.result.SessionReadResponse
+import com.google.android.gms.tasks.OnFailureListener
+import com.google.android.gms.tasks.OnSuccessListener
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
+import java.util.concurrent.TimeUnit
+import java.util.Date
+
+import java.util.concurrent.*
const val GOOGLE_FIT_PERMISSIONS_REQUEST_CODE = 1111
@@ -35,6 +38,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
private var result: Result? = null
private var handler: Handler? = null
private var activity: Activity? = null
+ private var threadPoolExecutor: ExecutorService? = null
private var BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE"
private var HEIGHT = "HEIGHT"
@@ -58,11 +62,14 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel?.setMethodCallHandler(this)
+ threadPoolExecutor = Executors.newFixedThreadPool(4)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = null
activity = null
+ threadPoolExecutor!!.shutdown()
+ threadPoolExecutor = null
}
// This static function is optional and equivalent to onAttachedToEngine. It supports the old
@@ -253,8 +260,6 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
}
val fitnessOptions = typesBuilder.build()
-
-
try {
val googleSignInAccount = GoogleSignIn.getAccountForExtension(activity!!.applicationContext, fitnessOptions)
Fitness.getHistoryClient(activity!!.applicationContext, googleSignInAccount)
@@ -272,6 +277,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
}
}
+
+
private fun getData(call: MethodCall, result: Result) {
if (activity == null) {
result.success(null)
@@ -285,143 +292,154 @@ class HealthPlugin(private var channel: MethodChannel? = null) : MethodCallHandl
// Look up data type and unit for the type key
val dataType = keyToHealthDataType(type)
val field = getField(type)
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType)
+ if (dataType == DataType.TYPE_SLEEP_SEGMENT) {
+ typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
+ }
+ val fitnessOptions = typesBuilder.build()
+ val googleSignInAccount = GoogleSignIn.getAccountForExtension(activity!!.applicationContext, fitnessOptions)
- /// Start a new thread for doing a GoogleFit data lookup
- thread {
- try {
- val typesBuilder = FitnessOptions.builder()
- typesBuilder.addDataType(dataType)
- if (dataType == DataType.TYPE_SLEEP_SEGMENT) {
- typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
- }
- val fitnessOptions = typesBuilder.build()
- val googleSignInAccount = GoogleSignIn.getAccountForExtension(activity!!.applicationContext, fitnessOptions)
-
- if (dataType != DataType.TYPE_SLEEP_SEGMENT) {
- val response = Fitness.getHistoryClient(activity!!.applicationContext, googleSignInAccount)
- .readData(DataReadRequest.Builder()
+ if (dataType != DataType.TYPE_SLEEP_SEGMENT) {
+ Fitness.getHistoryClient(activity!!.applicationContext, googleSignInAccount)
+ .readData(DataReadRequest.Builder()
.read(dataType)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build())
+ .addOnSuccessListener (threadPoolExecutor!!, dataHandler(dataType, field, result))
+ .addOnFailureListener(errHandler(result))
+ } else {
+ // request to the sessions for sleep data
+ val request = SessionReadRequest.Builder()
+ .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS)
+ .enableServerQueries()
+ .readSessionsFromAllApps()
+ .includeSleepSessions()
+ .build()
+ Fitness.getSessionsClient(activity!!.applicationContext, googleSignInAccount)
+ .readSession(request)
+ .addOnSuccessListener(threadPoolExecutor!!, sleepDataHandler(type, result))
+ .addOnFailureListener(errHandler(result))
+ }
+
+ }
+
+ private fun dataHandler(dataType: DataType, field: Field, result: Result) =
+ OnSuccessListener { response: DataReadResponse ->
+ /// Fetch all data points for the specified DataType
+ val dataSet = response.getDataSet(dataType)
+ /// For each data point, extract the contents and send them to Flutter, along with date and unit.
+ val healthData = dataSet.dataPoints.mapIndexed { _, dataPoint ->
+ return@mapIndexed hashMapOf(
+ "value" to getHealthDataValue(dataPoint, field),
+ "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS),
+ "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS),
+ "source_name" to (dataPoint.originalDataSource.appPackageName
+ ?: (dataPoint.originalDataSource.device?.model
+ ?: "")),
+ "source_id" to dataPoint.originalDataSource.streamIdentifier
+ )
+ }
+ activity!!.runOnUiThread { result.success(healthData) }
+ }
- /// Fetch all data points for the specified DataType
- val dataPoints = Tasks.await(response).getDataSet(dataType)
-
- /// For each data point, extract the contents and send them to Flutter, along with date and unit.
- val healthData = dataPoints.dataPoints.mapIndexed { _, dataPoint ->
- return@mapIndexed hashMapOf(
- "value" to getHealthDataValue(dataPoint, field),
- "date_from" to dataPoint.getStartTime(TimeUnit.MILLISECONDS),
- "date_to" to dataPoint.getEndTime(TimeUnit.MILLISECONDS),
- "source_name" to (dataPoint.originalDataSource.appPackageName ?: (dataPoint.originalDataSource.device?.model ?: "" )),
- "source_id" to dataPoint.originalDataSource.streamIdentifier
+ private fun errHandler(result: Result) = OnFailureListener { exception ->
+ activity!!.runOnUiThread { result.success(null) }
+ Log.i("FLUTTER_HEALTH::ERROR", exception.message ?: "unknown error")
+ Log.i("FLUTTER_HEALTH::ERROR", exception.stackTrace.toString())
+ }
+
+ private fun sleepDataHandler(type: String, result: Result) =
+ OnSuccessListener { response: SessionReadResponse ->
+ val healthData: MutableList