Skip to content

Commit

Permalink
More gracefull error handling (less trowing) and update to demo app a…
Browse files Browse the repository at this point in the history
…nd README
  • Loading branch information
bardram committed Sep 20, 2024
1 parent 09d3cdf commit ee231bd
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 132 deletions.
4 changes: 2 additions & 2 deletions packages/health/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
* Remove `includeManualEntry` (previously a boolean) from some of the querying methods in favor of `recordingMethodsToFilter`.
* For complete details on relevant changes, see the description of PR [#1023](https://github.com/cph-cachet/flutter-plugins/pull/1023)
* Add support for all sleep stages across iOS and Android
* Clean up relevant documentation
* Clean up relevant documentation
* Remove undocumented sleep stages
* **BREAKING** certain sleep stages were removed/combined into other related stages see PR [#1026](https://github.com/cph-cachet/flutter-plugins/pull/1026) for the complete list of changes and a discussion of the motivation in issue [#985](https://github.com/cph-cachet/flutter-plugins/issues/985)
* Android: Add support for `OTHER` workout type
* Cleaned up workout activity types for consistency across iOS and Android, see PR [#1020](https://github.com/cph-cachet/flutter-plugins/pull/1020) for a complete list of changes
* iOS: add support for menstruation flow, PR [#1008](https://github.com/cph-cachet/flutter-plugins/pull/1008)
* Android: Add support for heart rate variability, PR [#1009](https://github.com/cph-cachet/flutter-plugins/pull/1009)
* iOS: add support for atrial fibrillation burden, PR [#1031](https://github.com/cph-cachet/flutter-plugins/pull/1031)
* Add support for UUIDs in health records for both HealthKit and Health Connect, PR [#1019](https://github.com/cph-cachet/flutter-plugins/pull/1019)
* Add support for UUIDs in health records for both HealthKit and Health Connect, PR [#1019](https://github.com/cph-cachet/flutter-plugins/pull/1019)
* Fix an issue when querying workouts, the native code could respond with an activity that is not supported in the Health package, causing an error - this will fallback to `HealthWorkoutActivityType.other` - PR [#1016](https://github.com/cph-cachet/flutter-plugins/pull/1016)
* Remove deprecated Android v1 embeddings, PR [#1021](https://github.com/cph-cachet/flutter-plugins/pull/1021)

Expand Down
8 changes: 4 additions & 4 deletions packages/health/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Health

Enables reading and writing health data from/to Apple Health and Health Connect.
Enables reading and writing health data from/to [Apple Health](https://www.apple.com/health/) and [Google Health Connect](https://health.google/health-connect-android/).

> **NOTE:** Google has deprecated the Google Fit API. According to the [documentation](https://developers.google.com/fit/android), as of **May 1st 2024** developers cannot sign up for using the API. As such, this package has removed support for Google Fit as of version 11.0.0 and users are urged to upgrade as soon as possible.
Expand All @@ -17,7 +17,7 @@ The plugin supports:
- cleaning up duplicate data points via the `removeDuplicates` method.
- removing data of a given type in a selected period of time using the `delete` method.

Note that for Android, the target phone **needs** to have [Health Connect](https://health.google/health-connect-android/) (which is currently in beta) installed and have access to the internet, otherwise this plugin will not work.
Note that for Android, the target phone **needs** to have the [Health Connect](https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata&hl=en) app installed (which is currently in beta) and have access to the internet.

See the tables below for supported health and workout data types.

Expand Down Expand Up @@ -260,8 +260,8 @@ flutter: PlatformException(FlutterHealth, Results are null, Optional(Error Doma

Google Health Connect and Apple HealthKit both provide ways to distinguish samples collected "automatically" and manually entered data by the user.

- Android provides an enum with 4 variations: https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/metadata/Metadata#summary
- iOS has a boolean value: https://developer.apple.com/documentation/healthkit/hkmetadatakeywasuserentered
- Android provides an enum with 4 variations: <https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/metadata/Metadata#summary>
- iOS has a boolean value: <https://developer.apple.com/documentation/healthkit/hkmetadatakeywasuserentered>

As such, when fetching data you have the option to filter the fetched data by recording method as such:

Expand Down
181 changes: 94 additions & 87 deletions packages/health/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,18 @@ class _HealthAppState extends State<HealthApp> {
: HealthDataAccess.READ_WRITE)
.toList();

@override
void initState() {
// configure the health plugin before use.
// configure the health plugin before use and check the Health Connect status
Health().configure();
Health().getHealthConnectSdkStatus();

super.initState();
}

/// Install Google Health Connect on this phone.
Future<void> installHealthConnect() async {
await Health().installHealthConnect();
}
Future<void> installHealthConnect() async =>
await Health().installHealthConnect();

/// Authorize, i.e. get permissions to access relevant health data.
Future<void> authorize() async {
Expand Down Expand Up @@ -132,7 +133,8 @@ class _HealthAppState extends State<HealthApp> {
final status = await Health().getHealthConnectSdkStatus();

setState(() {
_contentHealthConnectStatus = Text('Health Connect Status: $status');
_contentHealthConnectStatus =
Text('Health Connect Status: ${status?.name.toUpperCase()}');
_state = AppState.HEALTH_CONNECT_STATUS;
});
}
Expand All @@ -143,7 +145,7 @@ class _HealthAppState extends State<HealthApp> {

// get data within the last 24 hours
final now = DateTime.now();
final yesterday = now.subtract(Duration(hours: 24));
final yesterday = now.subtract(const Duration(hours: 24));

// Clear old data points
_healthDataList.clear();
Expand Down Expand Up @@ -186,7 +188,7 @@ class _HealthAppState extends State<HealthApp> {
/// following data types.
Future<void> addData() async {
final now = DateTime.now();
final earlier = now.subtract(Duration(minutes: 20));
final earlier = now.subtract(const Duration(minutes: 20));

// Add data for supported types
// NOTE: These are only the ones supported on Androids new API Health Connect.
Expand Down Expand Up @@ -289,7 +291,7 @@ class _HealthAppState extends State<HealthApp> {
success &= await Health().writeWorkoutData(
activityType: HealthWorkoutActivityType.AMERICAN_FOOTBALL,
title: "Random workout name that shows up in Health Connect",
start: now.subtract(Duration(minutes: 15)),
start: now.subtract(const Duration(minutes: 15)),
end: now,
totalDistance: 2430,
totalEnergyBurned: 400,
Expand Down Expand Up @@ -378,7 +380,7 @@ class _HealthAppState extends State<HealthApp> {
/// Delete some random health data.
Future<void> deleteData() async {
final now = DateTime.now();
final earlier = now.subtract(Duration(hours: 24));
final earlier = now.subtract(const Duration(hours: 24));

bool success = true;
for (HealthDataType type in types) {
Expand Down Expand Up @@ -459,78 +461,82 @@ class _HealthAppState extends State<HealthApp> {
appBar: AppBar(
title: const Text('Health Example'),
),
body: Container(
child: Column(
children: [
Wrap(
spacing: 10,
children: [
body: Column(
children: [
Wrap(
spacing: 10,
children: [
if (Platform.isAndroid)
TextButton(
onPressed: authorize,
child: Text("Authenticate",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
if (Platform.isAndroid)
onPressed: getHealthConnectSdkStatus,
style: const ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blue)),
child: const Text("Check Health Connect Status",
style: TextStyle(color: Colors.white))),
if (Platform.isAndroid &&
Health().healthConnectSdkStatus !=
HealthConnectSdkStatus.sdkAvailable)
TextButton(
onPressed: installHealthConnect,
style: const ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blue)),
child: const Text("Install Health Connect",
style: TextStyle(color: Colors.white))),
if (Platform.isIOS ||
Platform.isAndroid &&
Health().healthConnectSdkStatus ==
HealthConnectSdkStatus.sdkAvailable)
Wrap(spacing: 10, children: [
TextButton(
onPressed: getHealthConnectSdkStatus,
child: Text("Check Health Connect Status",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
onPressed: authorize,
style: const ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
TextButton(
onPressed: fetchData,
child: Text("Fetch Data",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
TextButton(
onPressed: addData,
child: Text("Add Data",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
TextButton(
onPressed: deleteData,
child: Text("Delete Data",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
TextButton(
onPressed: fetchStepData,
child: Text("Fetch Step Data",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
TextButton(
onPressed: revokeAccess,
child: Text("Revoke Access",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
if (Platform.isAndroid)
WidgetStatePropertyAll(Colors.blue)),
child: const Text("Authenticate",
style: TextStyle(color: Colors.white))),
TextButton(
onPressed: installHealthConnect,
child: Text("Install Health Connect",
style: TextStyle(color: Colors.white)),
style: ButtonStyle(
onPressed: fetchData,
style: const ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.blue))),
],
),
Divider(thickness: 3),
if (_state == AppState.DATA_READY) _dataFiltration,
if (_state == AppState.STEPS_READY) _stepsFiltration,
Expanded(child: Center(child: _content))
],
),
WidgetStatePropertyAll(Colors.blue)),
child: const Text("Fetch Data",
style: TextStyle(color: Colors.white))),
TextButton(
onPressed: addData,
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll(Colors.blue)),
child: const Text("Add Data",
style: TextStyle(color: Colors.white))),
TextButton(
onPressed: deleteData,
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll(Colors.blue)),
child: const Text("Delete Data",
style: TextStyle(color: Colors.white))),
TextButton(
onPressed: fetchStepData,
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll(Colors.blue)),
child: const Text("Fetch Step Data",
style: TextStyle(color: Colors.white))),
TextButton(
onPressed: revokeAccess,
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll(Colors.blue)),
child: const Text("Revoke Access",
style: TextStyle(color: Colors.white))),
]),
],
),
const Divider(thickness: 3),
if (_state == AppState.DATA_READY) _dataFiltration,
if (_state == AppState.STEPS_READY) _stepsFiltration,
Expanded(child: Center(child: _content))
],
),
),
);
Expand Down Expand Up @@ -575,7 +581,7 @@ class _HealthAppState extends State<HealthApp> {
// Add other entries here if needed
],
),
Divider(thickness: 3),
const Divider(thickness: 3),
],
);

Expand Down Expand Up @@ -610,19 +616,19 @@ class _HealthAppState extends State<HealthApp> {
// Add other entries here if needed
],
),
Divider(thickness: 3),
const Divider(thickness: 3),
],
);

Widget get _permissionsRevoking => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(
padding: const EdgeInsets.all(20),
child: const CircularProgressIndicator(
strokeWidth: 10,
)),
Text('Revoking permissions...')
const Text('Revoking permissions...')
],
);

Expand All @@ -635,11 +641,11 @@ class _HealthAppState extends State<HealthApp> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(
padding: const EdgeInsets.all(20),
child: const CircularProgressIndicator(
strokeWidth: 10,
)),
Text('Fetching data...')
const Text('Fetching data...')
],
);

Expand Down Expand Up @@ -687,23 +693,24 @@ class _HealthAppState extends State<HealthApp> {

Widget _contentNoData = const Text('No Data to show');

Widget _contentNotFetched = const Column(children: [
Widget _contentNotFetched =
const Column(mainAxisAlignment: MainAxisAlignment.center, children: [
const Text("Press 'Auth' to get permissions to access health data."),
const Text("Press 'Fetch Dat' to get health data."),
const Text("Press 'Add Data' to add some random health data."),
const Text("Press 'Delete Data' to remove some random health data."),
], mainAxisAlignment: MainAxisAlignment.center);
]);

Widget _authorized = const Text('Authorization granted!');

Widget _authorizationNotGranted = const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Authorization not given.'),
const Text(
'For Google Health Connect please check if you have added the right permissions and services to the manifest file.'),
const Text('For Apple Health check your permissions in Apple Health.'),
],
mainAxisAlignment: MainAxisAlignment.center,
);

Widget _contentHealthConnectStatus = const Text(
Expand Down
Loading

0 comments on commit ee231bd

Please sign in to comment.