Rexios80 / polar

‎‎‎‎‎This is a Dart plugin wrapper for the Polar SDK on Android and iOS
https://pub.dev/packages/polar
BSD 3-Clause "New" or "Revised" License
17 stars 14 forks source link

Support for recordings #8

Closed robin-glimp closed 2 years ago

robin-glimp commented 2 years ago

I was going through the polar SDK and spotted the ability to record RR intervals over longer periods of time. I wanted to use this library to implement this feature in my app.

However I didnt see the right methods to do this. The only traces I can find for this support are:

Can you explain whether this feature is either not there, not scheduled to be implemented, still scheduled, something else?

Would love to have this as a part of the library and would be happy to test if needed.

My own devices:

Rexios80 commented 2 years ago

Can you link the documentation for this feature? I'm not sure if/when I'll have time to implement it (depending on how complicated it is), but that information will be useful regardless.

robin-glimp commented 2 years ago

Well the "documentation" I was referring to was the main README.md on the polar-ble-sdk repo.

H10 Heart rate sensor
- ...
- Start and stop of internal recording and request for internal recording status. Recording supports RR, HR with one second sampletime or HR with five second sampletime.
- List, read and remove for stored internal recording (sensor supports only one recording at the time).

The best overview of methods to be made available in this library would be found here: https://github.dev/polarofficial/polar-ble-sdk/blob/master/sources/Android/android-communications/library/src/sdk/java/com/polar/sdk/api/PolarBleApi.java#L296-L297

For my personal use case it would suffice to have a hard-coded exercise ID and drop the list functionality and simplify the signature of all the methods (dropping the exerciseId), however it is not according to the polar spec anymore then.

Rexios80 commented 2 years ago

The latest commit has the code for this, but it has literally zero testing. If you want to try it anyways you can use this in your pubspec:

  polar:
    git:
      url: https://github.com/Rexios80/polar
      ref: 5bcf251137220d6e108a7e020223b8c74c10d16f
robin-glimp commented 2 years ago

Thanks for the quick turnaround! Tested it out with my Android phone and Polar H10 sensor.

The listExercises and requestRecordingStatus return as expected: with an empty list and a filled object as a response. The issue I face is with the startRecording method, I am getting this error message:

PlatformException(error, java.lang.Integer cannot be cast to java.lang.String, null, java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at dev.rexios.polar.PolarPlugin.startRecording(PolarPlugin.kt:217)
    at dev.rexios.polar.PolarPlugin.onMethodCall(PolarPlugin.kt:76)
    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
    at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
    at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:319)
    at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:265)
    at android.app.ActivityThread.main(ActivityThread.java:8360)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)

With a further stackTrace:

#0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:607:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:167:18)
<asynchronous suspension>
#2      PolarProvider.startRecording (package:pebbles/providers/polar_provider.dart:234:7)
<asynchronous suspension>
#3      _DevToolsState._startRecording (package:pebbles/screens/dev_tools.dart:42:7)
<asynchronous suspension>

Looked at the code and it seems to be a conversion issue for the interval field or something? I cant write kotlin so not entirely sure.

Rexios80 commented 2 years ago

Yep that's what not testing it does 🙃

Rexios80 commented 2 years ago

Other things are probably still broken, but try this ref: "5d2c72a9c3adf2c0f39ceddec9534916c4a3800c"

robin-glimp commented 2 years ago

Bit by bit you'll figure it out with me being your tester haha. I'll give the new ref a go right now!

robin-glimp commented 2 years ago

Alright looks like I was able to make a successful recording. The next bug is in the listExercises command and the decoding of the response into a flutter object.

I have adapted your code so you can see the response object.

Code:

  Future<List<PolarExerciseEntry>> listExercises(String identifier) async {
    final result = await _channel.invokeListMethod('listExercises', identifier);
    if (result == null) {
      return [];
    }

    print(result);

    return result
        .cast<String>()
        .map((e) => PolarExerciseEntry.fromJson(jsonDecode(e)))
        .toList();
  }

The printed line is:

[{"date":"Oct 11, 2022 19:17:27","identifier":"test","path":"/test/SAMPLES.BPB"}]

And the stack trace for the operation is:

E/flutter (20684): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: type 'String' is not a subtype of type 'int'
E/flutter (20684): #0      new PolarExerciseEntry.fromJson (package:polar/src/model/polar_recording.dart:76:56)
E/flutter (20684): #1      Polar.listExercises.<anonymous closure> (package:polar/src/polar_base.dart:450:40)
E/flutter (20684): #2      MappedListIterable.elementAt (dart:_internal/iterable.dart:413:31)
E/flutter (20684): #3      ListIterator.moveNext (dart:_internal/iterable.dart:342:26)
E/flutter (20684): #4      new _GrowableList._ofEfficientLengthIterable (dart:core-patch/growable_array.dart:189:27)
E/flutter (20684): #5      new _GrowableList.of (dart:core-patch/growable_array.dart:150:28)
E/flutter (20684): #6      new List.of (dart:core-patch/array_patch.dart:51:28)
E/flutter (20684): #7      ListIterable.toList (dart:_internal/iterable.dart:213:44)
E/flutter (20684): #8      Polar.listExercises (package:polar/src/polar_base.dart:451:10)
E/flutter (20684): <asynchronous suspension>
E/flutter (20684): #9      PolarProvider.listExercises (package:pebbles/providers/polar_provider.dart:217:11)
E/flutter (20684): <asynchronous suspension>
E/flutter (20684): #10     _DevToolsState._listExercises (package:pebbles/screens/dev_tools.dart:57:9)
E/flutter (20684): <asynchronous suspension>

So it seems to be the date parsing.


Also another thing I found out: I wanted to try and get a second recording in there (which isnt possible for the H10 I believe). So I changed the exerciseId to test2 and hit record again. This gave me a PlatformException(OPERATION_NOT_PERMITTED, null, null, null). However as this library is purely a wrapper, this isnt your concern, as this is how the API specification is telling us how it should be. (Just a note to self to just use a hard-coded ID)

robin-glimp commented 2 years ago

Knowing the timestamp from the message above I also tried the fetchExercise method but there is another casting bug there.

polar.fetchExercise(
  PolarExerciseEntry(
    path: "/test/SAMPLES.BPB",
    date: DateTime(2022, 10, 11, 19, 17, 27),
    entryId: "test",
  ),
);

Results in:

PlatformException(error, java.util.HashMap cannot be cast to java.lang.String, null, java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String
    at dev.rexios.polar.PolarPlugin.fetchExercise(PolarPlugin.kt:287)
    at dev.rexios.polar.PolarPlugin.onMethodCall(PolarPlugin.kt:80)
    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
    at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
    at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:319)
    at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:265)
    at android.app.ActivityThread.main(ActivityThread.java:8360)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)

The same applies to the removeExercise parsing logic of the date.

Rexios80 commented 2 years ago

Try the ref "72034c005c7e74e1262511b9af73732e85df55fe"

robin-glimp commented 2 years ago

listExercises works good now!

3 new bugs:

  1. There is a mixup of the names entryId and identifier. This seems to be an issue in polar_recording.dart in the PolarExerciseEntry.fromJson method. The json name is identifier but it expects it to be entryId.
  2. fetchExercises fails with the following stacktrace:
    PlatformException(error, java.util.HashMap cannot be cast to java.lang.String, null, java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String
    at dev.rexios.polar.PolarPlugin.fetchExercise(PolarPlugin.kt:309)
    at dev.rexios.polar.PolarPlugin.onMethodCall(PolarPlugin.kt:102)
    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
    at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
    at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:319)
    at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:265)
    at android.app.ActivityThread.main(ActivityThread.java:8360)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
  3. removeExercise fails with the following stacktrace:
    PlatformException(error, java.util.HashMap cannot be cast to java.lang.String, null, java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String
    at dev.rexios.polar.PolarPlugin.removeExercise(PolarPlugin.kt:327)
    at dev.rexios.polar.PolarPlugin.onMethodCall(PolarPlugin.kt:103)
    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
    at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
    at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:319)
    at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:265)
    at android.app.ActivityThread.main(ActivityThread.java:8360)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
Rexios80 commented 2 years ago

Try "ffa98e5154a5f0952b95571f0591a30a8cba637a"

robin-glimp commented 2 years ago

@Rexios80 the issues on fetchExercise and removeExercise remains the same it seems:

PlatformException(error, java.util.HashMap cannot be cast to java.lang.String, null, java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String
    at dev.rexios.polar.PolarPlugin.removeExercise(PolarPlugin.kt:327)
    at dev.rexios.polar.PolarPlugin.onMethodCall(PolarPlugin.kt:103)
    at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
    at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
    at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:319)
    at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:265)
    at android.app.ActivityThread.main(ActivityThread.java:8360)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
Rexios80 commented 2 years ago

I think this fixes that "eb48cd071e46304181e63d12ae5e154340ef6331"

robin-glimp commented 2 years ago

You're a genius! I managed to start, stop, fetch, list and remove an exercise! I also love the named parameter addition for startRecording.

To get this all fully production ready we might need to look into error messages a bit more. So what happens if there are no entries found to fetch or starting another session while the H10 already has one saved.

Rexios80 commented 2 years ago
Rexios80 commented 2 years ago

Also I fixed some more issues on ref "99431a1b72e1ab6dafc3092949e5502a9dbb884b"

If you are able to test on iOS too so I could get another pair of eyes on the whole thing I would greatly appreciate it. If everything looks good to you, I'll go ahead and release it.

robin-glimp commented 2 years ago

One thing I keep seeing when I connect the polar is this stacktrace, it is not breaking, but it doesnt look good.

E/EventChannel#polar/streaming(30288): java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter arguments
E/EventChannel#polar/streaming(30288):  at dev.rexios.polar.PolarPlugin$streamingHandler$1.onCancel(Unknown Source:2)
E/EventChannel#polar/streaming(30288):  at io.flutter.plugin.common.EventChannel$IncomingStreamRequestHandler.onListen(EventChannel.java:212)
E/EventChannel#polar/streaming(30288):  at io.flutter.plugin.common.EventChannel$IncomingStreamRequestHandler.onMessage(EventChannel.java:197)
E/EventChannel#polar/streaming(30288):  at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
E/EventChannel#polar/streaming(30288):  at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$DartMessenger(DartMessenger.java:319)
E/EventChannel#polar/streaming(30288):  at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$TsixYUB5E6FpKhMtCSQVHKE89gQ.run(Unknown Source:12)
E/EventChannel#polar/streaming(30288):  at android.os.Handler.handleCallback(Handler.java:938)
E/EventChannel#polar/streaming(30288):  at android.os.Handler.dispatchMessage(Handler.java:99)
E/EventChannel#polar/streaming(30288):  at android.os.Looper.loop(Looper.java:265)
E/EventChannel#polar/streaming(30288):  at android.app.ActivityThread.main(ActivityThread.java:8360)
E/EventChannel#polar/streaming(30288):  at java.lang.reflect.Method.invoke(Native Method)
E/EventChannel#polar/streaming(30288):  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
E/EventChannel#polar/streaming(30288):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)

One more small issue on the latest ref. When I wake my phone up after a minute of inactivity and trigger the requestRecordingStatus I get this brief disconnect or something with the following Stacktrace. Only happened once or twice so not 100% sure whether I can consistently reproduce and whether its coming from the library, the polar library or android itself.

[log] PlatformException(com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpUtils$PftpOperationTimeout: Air packet was not received in required timeline, null, null, null)
[log] #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:607:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:167:18)
<asynchronous suspension>
#2      MethodChannel.invokeListMethod (package:flutter/src/services/platform_channel.dart:353:35)
<asynchronous suspension>
#3      Polar.requestRecordingStatus (package:polar/src/polar_base.dart:428:9)
<asynchronous suspension>
#4      PolarProvider.requestRecordingStatus (package:pebbles/providers/polar_provider.dart:259:14)
<asynchronous suspension>

I'm pretty sure you're supposed to call list before you call fetch, so that doesn't matter If you try to start a session with one already saved, you get an exception, so you should probably call list before trying that too

As this library is a wrapper I would say that is fine. But I do think some documentation on this would help. Maybe I can write some of that for you.

If you are able to test on iOS too so I could get another pair of eyes on the whole thing I would greatly appreciate it.

I should be able to do that for you later this week with a colleague of mine.

If everything looks good to you, I'll go ahead and release it.

Sure, any other tests beside iOS you want me to do? I also have a Polar verity sense here that I can use, however I havent been able to get good data from it so far. I really prefer the H10.

robin-glimp commented 2 years ago

So I recorded a 6 hour segment of RR intervals to test. The result of that is some errors..

PlatformException(com.polar.androidcommunications.api.ble.exceptions.BleCharacteristicNotificationNotEnabled: PS-FTP MTU not enabled, null, null, null)
PlatformException(com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpUtils$PftpOperationTimeout: Air packet was not received in required timeline, null, null, null)
PlatformException(Unknown error listing exercises, null, null, null)

Unfortunately I dont have stacktraces as these errors come from sentry. I got the second one the most often, but I think a clue lies in the first error --> FTP isnt enabled. This is also mentioned in the polar-ble-sdk, but I dont know how this plays into your wrapper and whether the app has to call a new function.

Wasn't able to test with iOS just yet. But thats in the pipeline.

robin-glimp commented 2 years ago

So the starting, stopping, listing, fetching recordings works but only for small recordings (few minutes max I think). After that it needs the file transfer but thats not working just yet.

Rexios80 commented 2 years ago

Are you getting an ftpFeatureReady call?

Rexios80 commented 2 years ago

It looks like you need ftp enabled to do anything with recording, so maybe you're just calling it too early?

Screen Shot 2022-10-13 at 11 57 14 AM
robin-glimp commented 2 years ago

I managed to get a download. the ftpFeatureReadyStream always just gives me the device identifier whenever I connect. But maybe that was sufficient to have the download. Still half of the time it fails and I cant really put my finger on the issue.

Bigger issue though: iOS cant connect to the polar anymore :( Here is the crash report from sentry.

OS Version: iOS 16.0.2 (20A380)
Report Version: 104

Exception Type: EXC_CRASH (SIGABRT)
Crashed Thread: 0

Application Specific Information:
State restoration of CBCentralManager is only allowed for applications that have specified the "bluetooth-central" background mode

Thread 0 Crashed:
0   CoreFoundation                  0x348a4a248         __exceptionPreprocess
1   libobjc.A.dylib                 0x33b1d7a64         objc_exception_throw
2   Foundation                      0x33d826818         -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
3   CoreBluetooth                   0x388351dc4         <redacted>
4   PolarBleSdk                     0x1014ec270         <unknown> + 292
5   PolarBleSdk                     0x1014f248c         <unknown> + 96
6   PolarBleSdk                     0x101547c7c         <unknown> + 1484
7   PolarBleSdk                     0x101546d54         PolarBleApiDefaultImpl.polarImplementation
8   polar                           0x1026c3c9c         <unknown> + 172
9   polar                           0x1026caef4         <unknown> + 116
10  polar                           0x1026c4828         <unknown> + 84
11  Flutter                         0x102e62e50         <redacted>
12  Flutter                         0x10294acd0         <redacted>
13  libdispatch.dylib               0x3573464b0         _dispatch_call_block_and_release
14  libdispatch.dylib               0x357347fd8         _dispatch_client_callout
15  libdispatch.dylib               0x3573567f0         _dispatch_main_queue_drain
16  libdispatch.dylib               0x357356440         _dispatch_main_queue_callback_4CF
17  CoreFoundation                  0x348adaa04         __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
18  CoreFoundation                  0x348abc364         __CFRunLoopRun
19  CoreFoundation                  0x348ac11e0         CFRunLoopRunSpecific
20  GraphicsServices                0x3ba781364         GSEventRunModal
21  UIKitCore                       0x34d0fed84         -[UIApplication _run]
22  UIKitCore                       0x34d0fe9e8         UIApplicationMain
23  Runner                          0x200140444         <redacted>
24  <unknown>                       0x1e00ed948         <redacted>

Thread 0 Crashed:
0   CoreFoundation                  0x348a4a248         __exceptionPreprocess
1   libobjc.A.dylib                 0x33b1d7a64         objc_exception_throw
2   Foundation                      0x33d826818         -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
3   CoreBluetooth                   0x388351dc4         <redacted>
4   PolarBleSdk                     0x1014ec270         <unknown> + 292
5   PolarBleSdk                     0x1014f248c         <unknown> + 96
6   PolarBleSdk                     0x101547c7c         <unknown> + 1484
7   PolarBleSdk                     0x101546d54         PolarBleApiDefaultImpl.polarImplementation
8   polar                           0x1026c3c9c         <unknown> + 172
9   polar                           0x1026caef4         <unknown> + 116
10  polar                           0x1026c4828         <unknown> + 84
11  Flutter                         0x102e62e50         <redacted>
12  Flutter                         0x10294acd0         <redacted>
13  libdispatch.dylib               0x3573464b0         _dispatch_call_block_and_release
14  libdispatch.dylib               0x357347fd8         _dispatch_client_callout
15  libdispatch.dylib               0x3573567f0         _dispatch_main_queue_drain
16  libdispatch.dylib               0x357356440         _dispatch_main_queue_callback_4CF
17  CoreFoundation                  0x348adaa04         __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
18  CoreFoundation                  0x348abc364         __CFRunLoopRun
19  CoreFoundation                  0x348ac11e0         CFRunLoopRunSpecific
20  GraphicsServices                0x3ba781364         GSEventRunModal
21  UIKitCore                       0x34d0fed84         -[UIApplication _run]
22  UIKitCore                       0x34d0fe9e8         UIApplicationMain
23  Runner                          0x200140444         <redacted>
24  <unknown>                       0x1e00ed948         <redacted>

My current Info.plist looks like this (I redacted my explanation)

<key>NSBluetoothAlwaysUsageDescription</key>
<string>...</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>...</string>

I will try this fix found in https://github.com/dotintent/FlutterBleLib/issues/530, maybe that helps. but this is then important for the documentation

Rexios80 commented 2 years ago

Well do you have the Bluetooth background mode enabled?

robin-glimp commented 2 years ago

Well do you have the Bluetooth background mode enabled?

Nope, so I will add that line in the Info.plist, however that was never required for me using this library before.

Rexios80 commented 2 years ago

It sounds like it's because of the state restoration stuff Polar added. I'm thinking my wrapper is good, and any remaining issues are to be discussed with the Polar devs themselves unless you can show me errors generated by my code.

Rexios80 commented 2 years ago

I released this to pub.dev in version 3.2.0

Rexios80 commented 2 years ago

Feel free to reopen this if it needs more attention