Commit 8ca91c8

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-04-23 15:41:55
implement bluetooth adapter cubit
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 7e017c3
app/android/app/src/main/AndroidManifest.xml
@@ -7,7 +7,23 @@
         </intent>
 
     </queries>
-   <application
+    <!-- Tell Google Play Store that your app uses Bluetooth LE
+     Set android:required="true" if bluetooth is necessary -->
+    <uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
+
+    <!-- New Bluetooth permissions in Android 12
+    https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+
+    <!-- legacy for Android 11 or lower -->
+    <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>
+
+    <!-- legacy for Android 9 or lower -->
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
+    <application
         android:label="blood pressure app"
         android:name="${applicationName}"
         android:icon="@mipmap/ic_launcher"
app/android/app/proguard-rules.pro
@@ -0,0 +1,1 @@
+-keep class com.lib.flutter_blue_plus.* { *; }
\ No newline at end of file
app/lib/bluetooth/bluetooth_cubit.dart
@@ -0,0 +1,78 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:blood_pressure_app/bluetooth/flutter_blue_plus_mockable.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+part 'bluetooth_state.dart';
+
+/// Availability of the devices bluetooth adapter.
+///
+/// The only state that allows using the adapter is [BluetoothReady].
+class BluetoothCubit extends Cubit<BluetoothState> {
+  /// Create a cubit connecting to the bluetooth module for availability.
+  ///
+  /// [flutterBluePlus] may be provided for testing purposes.
+  BluetoothCubit({
+    FlutterBluePlusMockable? flutterBluePlus
+  }): _flutterBluePlus = flutterBluePlus ?? FlutterBluePlusMockable(),
+        super(BluetoothInitial()) {
+    _adapterStateStateSubscription = _flutterBluePlus.adapterState.listen(_onAdapterStateChanged);
+  }
+
+  FlutterBluePlusMockable _flutterBluePlus;
+
+  BluetoothAdapterState _adapterState = BluetoothAdapterState.unknown;
+
+  late StreamSubscription<BluetoothAdapterState> _adapterStateStateSubscription;
+
+  @override
+  Future<void> close() async {
+    await _adapterStateStateSubscription.cancel();
+    await super.close();
+  }
+
+  void _onAdapterStateChanged(BluetoothAdapterState state) {
+    _adapterState = state;
+    switch (_adapterState) {
+      case BluetoothAdapterState.unavailable:
+        emit(BluetoothUnfeasible());
+      case BluetoothAdapterState.unauthorized:
+        emit(BluetoothUnauthorized());
+      case BluetoothAdapterState.on:
+        emit(BluetoothReady());
+      case BluetoothAdapterState.off:
+      case BluetoothAdapterState.turningOff:
+      case BluetoothAdapterState.turningOn:
+        emit(BluetoothDisabled());
+      case BluetoothAdapterState.unknown:
+        emit(BluetoothInitial());
+    }
+
+    /// Request the permission to connect to bluetooth devices.
+    Future<bool> requestPermission() async {
+      assert(_adapterState == BluetoothAdapterState.unauthorized, 'No need to '
+          'request permission when device unavailable or already authorized.');
+      assert(await Permission.bluetoothConnect.isGranted, 'Permissions handler'
+          'should report the same as blue_plus');
+      final permission = await Permission.bluetoothConnect.request();
+      return permission.isGranted;
+    }
+
+    /// Request to enable bluetooth on the device
+    Future<bool> enableBluetooth() async {
+      assert(state is BluetoothDisabled, 'No need to enable bluetooth when '
+          'already enabled or not known to be disabled.');
+      if (!Platform.isAndroid) return false;
+      try {
+        await _flutterBluePlus.turnOn();
+        return true;
+      } on FlutterBluePlusException {
+        return false;
+      }
+    }
+  }
+}
app/lib/bluetooth/bluetooth_state.dart
@@ -0,0 +1,25 @@
+part of 'bluetooth_cubit.dart';
+
+/// State of the devices bluetooth module.
+@immutable
+abstract class BluetoothState {}
+
+/// No information on whether bluetooth is available.
+///
+/// Options relating to bluetooth should only be shown where they don't disturb.
+class BluetoothInitial extends BluetoothState {}
+
+/// There is no way bluetooth will work (e.g. no sensor).
+///
+/// Options relating to bluetooth should not be shown.
+class BluetoothUnfeasible extends BluetoothState {}
+
+/// There is a bluetooth sensor but the app has no permission.
+class BluetoothUnauthorized extends BluetoothState {}
+
+/// The device has Bluetooth and the app has permissions, but it is disabled in
+/// the device settings.
+class BluetoothDisabled extends BluetoothState {}
+
+/// Bluetooth is ready for use by the app.
+class BluetoothReady extends BluetoothState {}
app/lib/bluetooth/flutter_blue_plus_mockable.dart
@@ -0,0 +1,139 @@
+import 'dart:async';
+
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+
+/// Wrapper for FlutterBluePlus in order to easily mock it
+/// Wraps all calls for testing purposes
+class FlutterBluePlusMockable {
+  LogLevel get logLevel => FlutterBluePlus.logLevel;
+
+  /// Checks whether the hardware supports Bluetooth
+  Future<bool> get isSupported => FlutterBluePlus.isSupported;
+
+  /// The current adapter state
+  BluetoothAdapterState get adapterStateNow => FlutterBluePlus.adapterStateNow;
+
+  /// Return the friendly Bluetooth name of the local Bluetooth adapter
+  Future<String> get adapterName => FlutterBluePlus.adapterName;
+
+  /// returns whether we are scanning as a stream
+  Stream<bool> get isScanning => FlutterBluePlus.isScanning;
+
+  /// are we scanning right now?
+  bool get isScanningNow => FlutterBluePlus.isScanningNow;
+
+  /// the most recent scan results
+  List<ScanResult> get lastScanResults => FlutterBluePlus.lastScanResults;
+
+  /// a stream of scan results
+  /// - if you re-listen to the stream it re-emits the previous results
+  /// - the list contains all the results since the scan started
+  /// - the returned stream is never closed.
+  Stream<List<ScanResult>> get scanResults => FlutterBluePlus.scanResults;
+
+  /// This is the same as scanResults, except:
+  /// - it *does not* re-emit previous results after scanning stops.
+  Stream<List<ScanResult>> get onScanResults => FlutterBluePlus.onScanResults;
+
+  /// Get access to all device event streams
+  BluetoothEvents get events => FlutterBluePlus.events;
+
+  /// Gets the current state of the Bluetooth module
+  Stream<BluetoothAdapterState> get adapterState =>
+      FlutterBluePlus.adapterState;
+
+  /// Retrieve a list of devices currently connected to your app
+  List<BluetoothDevice> get connectedDevices =>
+      FlutterBluePlus.connectedDevices;
+
+  /// Retrieve a list of devices currently connected to the system
+  /// - The list includes devices connected to by *any* app
+  /// - You must still call device.connect() to connect them to *your app*
+  Future<List<BluetoothDevice>> get systemDevices =>
+      FlutterBluePlus.systemDevices;
+
+  /// Retrieve a list of bonded devices (Android only)
+  Future<List<BluetoothDevice>> get bondedDevices =>
+      FlutterBluePlus.bondedDevices;
+
+  /// Set configurable options
+  ///   - [showPowerAlert] Whether to show the power alert (iOS & MacOS only). i.e. CBCentralManagerOptionShowPowerAlertKey
+  ///       To set this option you must call this method before any other method in this package.
+  ///       See: https://developer.apple.com/documentation/corebluetooth/cbcentralmanageroptionshowpoweralertkey
+  ///       This option has no effect on Android.
+  Future<void> setOptions({
+    bool showPowerAlert = true,
+  }) => FlutterBluePlus.setOptions(showPowerAlert: showPowerAlert);
+  
+  /// Turn on Bluetooth (Android only),
+  Future<void> turnOn({int timeout = 60}) => 
+      FlutterBluePlus.turnOn(timeout: timeout);
+
+  /// Start a scan, and return a stream of results
+  /// Note: scan filters use an "or" behavior. i.e. if you set `withServices` & `withNames` we
+  /// return all the advertisments that match any of the specified services *or* any of the specified names.
+  ///   - [withServices] filter by advertised services
+  ///   - [withRemoteIds] filter for known remoteIds (iOS: 128-bit guid, android: 48-bit mac address)
+  ///   - [withNames] filter by advertised names (exact match)
+  ///   - [withKeywords] filter by advertised names (matches any substring)
+  ///   - [withMsd] filter by manfacture specific data
+  ///   - [withServiceData] filter by service data
+  ///   - [timeout] calls stopScan after a specified duration
+  ///   - [removeIfGone] if true, remove devices after they've stopped advertising for X duration
+  ///   - [continuousUpdates] If `true`, we continually update 'lastSeen' & 'rssi' by processing
+  ///        duplicate advertisements. This takes more power. You typically should not use this option.
+  ///   - [continuousDivisor] Useful to help performance. If divisor is 3, then two-thirds of advertisements are
+  ///        ignored, and one-third are processed. This reduces main-thread usage caused by the platform channel.
+  ///        The scan counting is per-device so you always get the 1st advertisement from each device.
+  ///        If divisor is 1, all advertisements are returned. This argument only matters for `continuousUpdates` mode.
+  ///   - [oneByOne] if `true`, we will stream every advertistment one by one, possibly including duplicates.
+  ///        If `false`, we deduplicate the advertisements, and return a list of devices.
+  ///   - [androidScanMode] choose the android scan mode to use when scanning
+  ///   - [androidUsesFineLocation] request `ACCESS_FINE_LOCATION` permission at runtime
+  Future<void> startScan({
+    List<Guid> withServices = const [],
+    List<String> withRemoteIds = const [],
+    List<String> withNames = const [],
+    List<String> withKeywords = const [],
+    List<MsdFilter> withMsd = const [],
+    List<ServiceDataFilter> withServiceData = const [],
+    Duration? timeout,
+    Duration? removeIfGone,
+    bool continuousUpdates = false,
+    int continuousDivisor = 1,
+    bool oneByOne = false,
+    AndroidScanMode androidScanMode = AndroidScanMode.lowLatency,
+    bool androidUsesFineLocation = false,
+  }) => FlutterBluePlus.startScan(
+    withServices: withServices,
+    withRemoteIds: withRemoteIds,
+    withNames: withNames,
+    withKeywords: withKeywords,
+    withMsd: withMsd,
+    withServiceData: withServiceData,
+    timeout: timeout,
+    removeIfGone: removeIfGone,
+    continuousUpdates: continuousUpdates,
+    continuousDivisor: continuousDivisor,
+    oneByOne: oneByOne,
+    androidScanMode: androidScanMode,
+    androidUsesFineLocation: androidUsesFineLocation,
+  );
+
+  /// Stops a scan for Bluetooth Low Energy devices
+  Future<void> stopScan() => FlutterBluePlus.stopScan();
+
+  /// Register a subscription to be canceled when scanning is complete.
+  /// This function simplifies cleanup, to prevent creating duplicate stream subscriptions.
+  ///   - this is an optional convenience function
+  ///   - prevents accidentally creating duplicate subscriptions before each scan
+  void cancelWhenScanComplete(StreamSubscription subscription) =>
+      FlutterBluePlus.cancelWhenScanComplete(subscription);
+
+  /// Sets the internal FlutterBlue log level
+  Future<void> setLogLevel(LogLevel level, {bool color = true}) =>
+      FlutterBluePlus.setLogLevel(level, color: color);
+
+  /// Request Bluetooth PHY support
+  Future<PhySupport> getPhySupport() => FlutterBluePlus.getPhySupport();
+}
\ No newline at end of file
app/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,12 +5,14 @@
 import FlutterMacOS
 import Foundation
 
+import flutter_blue_plus
 import package_info_plus
 import shared_preferences_foundation
 import sqflite
 import url_launcher_macos
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
   FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
app/test/bluetooth/bluetooth_cubit_test.dart
@@ -0,0 +1,42 @@
+import 'dart:async';
+
+import 'package:blood_pressure_app/bluetooth/bluetooth_cubit.dart';
+import 'package:blood_pressure_app/bluetooth/flutter_blue_plus_mockable.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+@GenerateNiceMocks([MockSpec<FlutterBluePlusMockable>()])
+import 'bluetooth_cubit_test.mocks.dart';
+
+void main() {
+  test('should translate adapter stream to state', () async {
+    final bluePlus = MockFlutterBluePlusMockable();
+    when(bluePlus.adapterState).thenAnswer((_) =>
+      Stream.fromIterable([
+        BluetoothAdapterState.unknown,
+        BluetoothAdapterState.unavailable,
+        BluetoothAdapterState.turningOff,
+        BluetoothAdapterState.off,
+        BluetoothAdapterState.unauthorized,
+        BluetoothAdapterState.turningOn,
+        BluetoothAdapterState.on,
+    ]));
+    final cubit = BluetoothCubit(flutterBluePlus: bluePlus);
+    expect(cubit.state, isA<BluetoothInitial>());
+
+    await expectLater(cubit.stream, emitsInOrder([
+      isA<BluetoothInitial>(),
+      isA<BluetoothUnfeasible>(),
+      isA<BluetoothDisabled>(),
+      isA<BluetoothDisabled>(),
+      isA<BluetoothUnauthorized>(),
+      isA<BluetoothDisabled>(),
+      isA<BluetoothReady>(),
+    ]));
+  });
+  // TODO: integration tests ?
+  test('should request permissions', () async {});
+  test('should enable bluetooth', () async {});
+}
app/pubspec.lock
@@ -5,18 +5,18 @@ packages:
     dependency: transitive
     description:
       name: _fe_analyzer_shared
-      sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
+      sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
       url: "https://pub.dev"
     source: hosted
-    version: "67.0.0"
+    version: "64.0.0"
   analyzer:
     dependency: transitive
     description:
       name: analyzer
-      sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
+      sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
       url: "https://pub.dev"
     source: hosted
-    version: "6.4.1"
+    version: "6.2.0"
   archive:
     dependency: transitive
     description:
@@ -81,6 +81,46 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.4.1"
+  build_config:
+    dependency: transitive
+    description:
+      name: build_config
+      sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.1"
+  build_daemon:
+    dependency: transitive
+    description:
+      name: build_daemon
+      sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.1"
+  build_resolvers:
+    dependency: transitive
+    description:
+      name: build_resolvers
+      sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.2"
+  build_runner:
+    dependency: "direct dev"
+    description:
+      name: build_runner
+      sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.9"
+  build_runner_core:
+    dependency: transitive
+    description:
+      name: build_runner_core
+      sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.3.0"
   built_collection:
     dependency: transitive
     description:
@@ -113,6 +153,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.1"
+  checked_yaml:
+    dependency: transitive
+    description:
+      name: checked_yaml
+      sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.3"
   clock:
     dependency: transitive
     description:
@@ -138,7 +186,7 @@ packages:
     source: hosted
     version: "1.18.0"
   convert:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: convert
       sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
@@ -189,10 +237,10 @@ packages:
     dependency: transitive
     description:
       name: ffi
-      sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
+      sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
       url: "https://pub.dev"
     source: hosted
-    version: "2.1.2"
+    version: "2.1.0"
   file:
     dependency: "direct dev"
     description:
@@ -238,6 +286,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "8.1.4"
+  flutter_blue_plus:
+    dependency: "direct main"
+    description:
+      name: flutter_blue_plus
+      sha256: "9419ca515ddcbe19a87fa2c45df97833d258c939b0e39ee2f38688a6c1fdbfde"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.32.4"
   flutter_lints:
     dependency: "direct dev"
     description:
@@ -255,10 +311,10 @@ packages:
     dependency: "direct main"
     description:
       name: flutter_markdown
-      sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db
+      sha256: "5b24061317f850af858ef7151dadbb6eb77c1c449c954c7bb064e8a5e0e7d81f"
       url: "https://pub.dev"
     source: hosted
-    version: "0.6.21"
+    version: "0.6.20"
   flutter_plugin_android_lifecycle:
     dependency: transitive
     description:
@@ -267,14 +323,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.0.17"
-  flutter_reactive_ble:
-    dependency: "direct main"
-    description:
-      name: flutter_reactive_ble
-      sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
-      url: "https://pub.dev"
-    source: hosted
-    version: "5.3.1"
   flutter_test:
     dependency: "direct dev"
     description: flutter
@@ -301,6 +349,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.4.1"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.0"
   function_tree:
     dependency: "direct main"
     description:
@@ -309,14 +365,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.9.1"
-  functional_data:
-    dependency: transitive
-    description:
-      name: functional_data
-      sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
-      url: "https://pub.dev"
-    source: hosted
-    version: "1.1.1"
   glob:
     dependency: transitive
     description:
@@ -325,6 +373,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.2"
+  graphs:
+    dependency: transitive
+    description:
+      name: graphs
+      sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.1"
   health_data_store:
     dependency: "direct main"
     description:
@@ -336,10 +392,18 @@ packages:
     dependency: transitive
     description:
       name: http
-      sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
+      sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.1"
+    version: "1.2.0"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.1"
   http_parser:
     dependency: transitive
     description:
@@ -364,6 +428,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.18.1"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
   js:
     dependency: transitive
     description:
@@ -388,30 +460,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.8.1"
-  leak_tracker:
-    dependency: transitive
-    description:
-      name: leak_tracker
-      sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
-      url: "https://pub.dev"
-    source: hosted
-    version: "10.0.4"
-  leak_tracker_flutter_testing:
-    dependency: transitive
-    description:
-      name: leak_tracker_flutter_testing
-      sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
-      url: "https://pub.dev"
-    source: hosted
-    version: "3.0.3"
-  leak_tracker_testing:
-    dependency: transitive
-    description:
-      name: leak_tracker_testing
-      sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
-      url: "https://pub.dev"
-    source: hosted
-    version: "3.0.1"
   lints:
     dependency: transitive
     description:
@@ -440,26 +488,34 @@ packages:
     dependency: transitive
     description:
       name: matcher
-      sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+      sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
       url: "https://pub.dev"
     source: hosted
-    version: "0.12.16+1"
+    version: "0.12.16"
   material_color_utilities:
     dependency: transitive
     description:
       name: material_color_utilities
-      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
       url: "https://pub.dev"
     source: hosted
-    version: "0.8.0"
+    version: "0.5.0"
   meta:
     dependency: transitive
     description:
       name: meta
-      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+      sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
       url: "https://pub.dev"
     source: hosted
-    version: "1.12.0"
+    version: "1.10.0"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.5"
   mockito:
     dependency: "direct dev"
     description:
@@ -504,10 +560,10 @@ packages:
     dependency: "direct main"
     description:
       name: path
-      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+      sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
       url: "https://pub.dev"
     source: hosted
-    version: "1.9.0"
+    version: "1.8.3"
   path_parsing:
     dependency: transitive
     description:
@@ -628,14 +684,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.7.4"
-  protobuf:
+  pool:
     dependency: transitive
     description:
-      name: protobuf
-      sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08"
+      name: pool
+      sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
       url: "https://pub.dev"
     source: hosted
-    version: "2.1.0"
+    version: "1.5.1"
   provider:
     dependency: "direct main"
     description:
@@ -652,30 +708,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.4"
-  qr:
+  pubspec_parse:
     dependency: transitive
     description:
-      name: qr
-      sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
+      name: pubspec_parse
+      sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
-  reactive_ble_mobile:
-    dependency: transitive
-    description:
-      name: reactive_ble_mobile
-      sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
-      url: "https://pub.dev"
-    source: hosted
-    version: "5.3.1"
-  reactive_ble_platform_interface:
+    version: "1.2.3"
+  qr:
     dependency: transitive
     description:
-      name: reactive_ble_platform_interface
-      sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
+      name: qr
+      sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
       url: "https://pub.dev"
     source: hosted
-    version: "5.3.1"
+    version: "3.0.1"
   restart_app:
     dependency: "direct main"
     description:
@@ -728,10 +776,10 @@ packages:
     dependency: transitive
     description:
       name: shared_preferences_web
-      sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
+      sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
       url: "https://pub.dev"
     source: hosted
-    version: "2.3.0"
+    version: "2.2.2"
   shared_preferences_windows:
     dependency: transitive
     description:
@@ -740,6 +788,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.3.2"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.1"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -817,6 +881,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.2"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
   string_scanner:
     dependency: transitive
     description:
@@ -845,10 +917,18 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
+      sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.0"
+    version: "0.6.1"
+  timing:
+    dependency: transitive
+    description:
+      name: timing
+      sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.1"
   translations_cleaner:
     dependency: "direct dev"
     description:
@@ -885,10 +965,10 @@ packages:
     dependency: transitive
     description:
       name: url_launcher_ios
-      sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
+      sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
       url: "https://pub.dev"
     source: hosted
-    version: "6.2.5"
+    version: "6.2.4"
   url_launcher_linux:
     dependency: transitive
     description:
@@ -917,10 +997,10 @@ packages:
     dependency: transitive
     description:
       name: url_launcher_web
-      sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
+      sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
       url: "https://pub.dev"
     source: hosted
-    version: "2.3.0"
+    version: "2.2.3"
   url_launcher_windows:
     dependency: transitive
     description:
@@ -937,14 +1017,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.4"
-  vm_service:
-    dependency: transitive
-    description:
-      name: vm_service
-      sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246
-      url: "https://pub.dev"
-    source: hosted
-    version: "14.0.0"
   watcher:
     dependency: transitive
     description:
@@ -957,18 +1029,26 @@ packages:
     dependency: transitive
     description:
       name: web
-      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
+      sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.1"
+    version: "0.3.0"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.0"
   win32:
     dependency: transitive
     description:
       name: win32
-      sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
+      sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
       url: "https://pub.dev"
     source: hosted
-    version: "5.3.0"
+    version: "5.2.0"
   xdg_directories:
     dependency: transitive
     description:
@@ -994,5 +1074,5 @@ packages:
     source: hosted
     version: "3.1.2"
 sdks:
-  dart: ">=3.3.0 <4.0.0"
-  flutter: ">=3.19.0"
+  dart: ">=3.2.0 <4.0.0"
+  flutter: ">=3.16.0"
app/pubspec.yaml
@@ -29,8 +29,9 @@ dependencies:
     path: ../health_data_store/
   sqlparser: ^0.34.1
   flutter_bloc: ^8.1.4
-  flutter_reactive_ble: ^5.3.1
   permission_handler: ^11.3.1
+  convert: ^3.1.1
+  flutter_blue_plus: ^1.32.4
 
   # can become one custom dependency
   file_picker: ^5.2.11  # MIT
@@ -46,6 +47,7 @@ dev_dependencies:
   mockito: ^5.4.1
   sqflite_common_ffi: ^2.3.0
   translations_cleaner: ^0.0.5
+  build_runner: ^2.4.9
 
 flutter:
   uses-material-design: true
health_data_store/pubspec.yaml
@@ -4,7 +4,7 @@ version: 0.1.0+1
 publish_to: none
 
 environment:
-  sdk: "^3.3.0"
+  sdk: '>=3.0.2 <4.0.0'
 
 dependencies:
   freezed_annotation: ^2.4.1