Skip to content

Add Unit Support for Health Data Types & Handle Rate Limiting #1204

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

connor-callaghan
Copy link

@connor-callaghan connor-callaghan commented May 26, 2025

Add Unit Support for Health Data Types

Changes

This PR adds further unit conversion support to the Health's Android implementation, specifically for Health Connect. Key improvements:

Stops crashes discussed in #1028 (underlying issue should be fixed in another pr)

Android (Kotlin)

  • Added support for multiple unit types when reading and writing health data
  • Implemented unit conversion functions for common health measurements:
    • Weight/Mass: kg, g, lb, oz, stone
    • Length: m, cm, in, ft
    • Temperature: °C, °F, K
    • Blood glucose: mmol/L, mg/dL
    • Volume: L, mL

Dart API Enhancements

  • Added support for specifying preferred units when retrieving health data
  • Added optional clientRecordId and clientRecordVersion parameters for better data tracking
  • Updated HealthDataPoint factory to properly handle unit conversions
  • Added CENTIMETER and MILLIMOLES_PER_LITER to HealthDataUnit enum

Benefits

  • More flexibility for app developers to work with units preferred by their users
  • Better support for international apps (metric vs imperial units)
  • Improved consistency with iOS implementation
  • Maintains backward compatibility with existing code

This implementation preserves all existing functionality while extending the plugin's capabilities to handle different measurement units consistently.

@connor-callaghan connor-callaghan changed the title Add Unit Support for Health Data Types Add Unit Support for Health Data Types & Handle Rate Limiting May 28, 2025
@iarata iarata self-assigned this Jun 3, 2025
@iarata
Copy link
Contributor

iarata commented Jun 3, 2025

Thank you for the PR,

Why the following check was removed from getHealthConnectSdkStatus

if (healthConnectAvailable) {
    healthConnectClient =
         HealthConnectClient.getOrCreate(
             context!!
         )
}

Also, when running the example app it returns with the following error:

E/MethodChannel#flutter_health( 5630): Failed to handle method call
E/MethodChannel#flutter_health( 5630): java.lang.NullPointerException
E/MethodChannel#flutter_health( 5630):  at cachet.plugins.health.HealthPlugin.writeData(HealthPlugin.kt:1771)
E/MethodChannel#flutter_health( 5630):  at cachet.plugins.health.HealthPlugin.onMethodCall(HealthPlugin.kt:165)
E/MethodChannel#flutter_health( 5630):  at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
E/MethodChannel#flutter_health( 5630):  at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/MethodChannel#flutter_health( 5630):  at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/MethodChannel#flutter_health( 5630):  at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/MethodChannel#flutter_health( 5630):  at android.os.Handler.handleCallback(Handler.java:995)
E/MethodChannel#flutter_health( 5630):  at android.os.Handler.dispatchMessage(Handler.java:103)
E/MethodChannel#flutter_health( 5630):  at android.os.Looper.loopOnce(Looper.java:248)
E/MethodChannel#flutter_health( 5630):  at android.os.Looper.loop(Looper.java:338)
E/MethodChannel#flutter_health( 5630):  at android.app.ActivityThread.main(ActivityThread.java:9067)
E/MethodChannel#flutter_health( 5630):  at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#flutter_health( 5630):  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
E/MethodChannel#flutter_health( 5630):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
E/flutter ( 5630): [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: PlatformException(error, null, null, java.lang.NullPointerException
E/flutter ( 5630):      at cachet.plugins.health.HealthPlugin.writeData(HealthPlugin.kt:1771)
E/flutter ( 5630):      at cachet.plugins.health.HealthPlugin.onMethodCall(HealthPlugin.kt:165)
E/flutter ( 5630):      at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
E/flutter ( 5630):      at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/flutter ( 5630):      at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/flutter ( 5630):      at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/flutter ( 5630):      at android.os.Handler.handleCallback(Handler.java:995)
E/flutter ( 5630):      at android.os.Handler.dispatchMessage(Handler.java:103)
E/flutter ( 5630):      at android.os.Looper.loopOnce(Looper.java:248)
E/flutter ( 5630):      at android.os.Looper.loop(Looper.java:338)
E/flutter ( 5630):      at android.app.ActivityThread.main(ActivityThread.java:9067)
E/flutter ( 5630):      at java.lang.reflect.Method.invoke(Native Method)
E/flutter ( 5630):      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
E/flutter ( 5630):      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
E/flutter ( 5630): )
E/flutter ( 5630): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:652:7)
E/flutter ( 5630): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:370:18)
E/flutter ( 5630): <asynchronous suspension>
E/flutter ( 5630): #2      Health.writeHealthData (package:health/src/health_plugin.dart:545:21)
E/flutter ( 5630): <asynchronous suspension>
E/flutter ( 5630): #3      HealthAppState.addData (package:health_example/main.dart:214:16)
E/flutter ( 5630): <asynchronous suspension>
E/flutter ( 5630): 

@iarata
Copy link
Contributor

iarata commented Jun 5, 2025

We are testing the refactored Android version for Health (See PR #1211 - Branch health13/refactor-kotlin). Could you modify your code to match the refactored version?

@connor-callaghan
Copy link
Author

Thank you for the PR,

Why the following check was removed from getHealthConnectSdkStatus

if (healthConnectAvailable) {
    healthConnectClient =
         HealthConnectClient.getOrCreate(
             context!!
         )
}

Also, when running the example app it returns with the following error:

E/MethodChannel#flutter_health( 5630): Failed to handle method call
E/MethodChannel#flutter_health( 5630): java.lang.NullPointerException
E/MethodChannel#flutter_health( 5630):  at cachet.plugins.health.HealthPlugin.writeData(HealthPlugin.kt:1771)
E/MethodChannel#flutter_health( 5630):  at cachet.plugins.health.HealthPlugin.onMethodCall(HealthPlugin.kt:165)
E/MethodChannel#flutter_health( 5630):  at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
E/MethodChannel#flutter_health( 5630):  at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/MethodChannel#flutter_health( 5630):  at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/MethodChannel#flutter_health( 5630):  at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/MethodChannel#flutter_health( 5630):  at android.os.Handler.handleCallback(Handler.java:995)
E/MethodChannel#flutter_health( 5630):  at android.os.Handler.dispatchMessage(Handler.java:103)
E/MethodChannel#flutter_health( 5630):  at android.os.Looper.loopOnce(Looper.java:248)
E/MethodChannel#flutter_health( 5630):  at android.os.Looper.loop(Looper.java:338)
E/MethodChannel#flutter_health( 5630):  at android.app.ActivityThread.main(ActivityThread.java:9067)
E/MethodChannel#flutter_health( 5630):  at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#flutter_health( 5630):  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
E/MethodChannel#flutter_health( 5630):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
E/flutter ( 5630): [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: PlatformException(error, null, null, java.lang.NullPointerException
E/flutter ( 5630):      at cachet.plugins.health.HealthPlugin.writeData(HealthPlugin.kt:1771)
E/flutter ( 5630):      at cachet.plugins.health.HealthPlugin.onMethodCall(HealthPlugin.kt:165)
E/flutter ( 5630):      at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
E/flutter ( 5630):      at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
E/flutter ( 5630):      at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
E/flutter ( 5630):      at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
E/flutter ( 5630):      at android.os.Handler.handleCallback(Handler.java:995)
E/flutter ( 5630):      at android.os.Handler.dispatchMessage(Handler.java:103)
E/flutter ( 5630):      at android.os.Looper.loopOnce(Looper.java:248)
E/flutter ( 5630):      at android.os.Looper.loop(Looper.java:338)
E/flutter ( 5630):      at android.app.ActivityThread.main(ActivityThread.java:9067)
E/flutter ( 5630):      at java.lang.reflect.Method.invoke(Native Method)
E/flutter ( 5630):      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
E/flutter ( 5630):      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
E/flutter ( 5630): )
E/flutter ( 5630): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:652:7)
E/flutter ( 5630): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:370:18)
E/flutter ( 5630): <asynchronous suspension>
E/flutter ( 5630): #2      Health.writeHealthData (package:health/src/health_plugin.dart:545:21)
E/flutter ( 5630): <asynchronous suspension>
E/flutter ( 5630): #3      HealthAppState.addData (package:health_example/main.dart:214:16)
E/flutter ( 5630): <asynchronous suspension>
E/flutter ( 5630): 

@iarata Thanks for looking at this, the check removal was a mistake. I will update this pr, adding back the check, a fix for the logs, and a sync with #1211 shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants