Commit fca30031 authored by Nicolas Richard Walter Boeckh's avatar Nicolas Richard Walter Boeckh 💬

Merge branch 'ux-context-aware-and-last-prefs' into 'main'

Ux context aware and last prefs

See merge request !9
parents cf419147 84356333
This diff is collapsed.
include: package:pedantic/analysis_options.yaml
analyzer:
exclude:
- lib\localization\*
linter:
rules:
unnecessary_this: false
omit_local_variable_types: false
\ No newline at end of file
...@@ -38,7 +38,6 @@ android { ...@@ -38,7 +38,6 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.logair.logair_application" applicationId "com.logair.logair_application"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 28 targetSdkVersion 28
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application <application
android:name="io.flutter.app.FlutterApplication" android:name="io.flutter.app.FlutterApplication"
android:label="logair.io" android:label="logair.io"
......
...@@ -4,4 +4,8 @@ ...@@ -4,4 +4,8 @@
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
</manifest> </manifest>
...@@ -23,7 +23,7 @@ class HomeController { ...@@ -23,7 +23,7 @@ class HomeController {
this._triggerOriginNavbar = true; this._triggerOriginNavbar = true;
} }
static final HomeController _singleton = new HomeController._internal(); static final HomeController _singleton = HomeController._internal();
/// The controller for the [CarouselSlider]. /// The controller for the [CarouselSlider].
PageController _pageController; PageController _pageController;
...@@ -42,12 +42,14 @@ class HomeController { ...@@ -42,12 +42,14 @@ class HomeController {
/// Forces the [CircularBottomNavigation] to the correct position. /// Forces the [CircularBottomNavigation] to the correct position.
set navbar(int page) { set navbar(int page) {
this._currentPage = page; this._currentPage = page;
// Set to false to mitigate livelock. // Set to false to mitigate livelock.
this._triggerOriginNavbar = false; this._triggerOriginNavbar = false;
// Guard // Guard
if (this._navigationController != null) if (this._navigationController != null) {
this._navigationController.value = (this._currentPage + 1) % 3; this._navigationController.value = (this._currentPage + 1) % 3;
}
this._previousPage = this._currentPage; this._previousPage = this._currentPage;
// Set to true once risk of livelock passed. // Set to true once risk of livelock passed.
...@@ -62,11 +64,12 @@ class HomeController { ...@@ -62,11 +64,12 @@ class HomeController {
this._currentPage = (page - 1) % 3; this._currentPage = (page - 1) % 3;
// Make the carousel controller move forward (jump to +1 would limit UI because of lackluster cyclical handling). // Make the carousel controller move forward (jump to +1 would limit UI because of lackluster cyclical handling).
if ((this._currentPage == 0 && this._previousPage == 2) || (this._currentPage == 1 && this._previousPage == 0) || (this._currentPage == 2 && this._previousPage == 1)) if ((this._currentPage == 0 && this._previousPage == 2) || (this._currentPage == 1 && this._previousPage == 0) || (this._currentPage == 2 && this._previousPage == 1)) {
this._pageController.nextPage(duration: Duration(milliseconds: 200), curve: Curves.linear); this._pageController.nextPage(duration: Duration(milliseconds: 200), curve: Curves.linear);
// Make the carousel controller move backward (jump to +1 would limit UI because of lackluster cyclical handling). // Make the carousel controller move backward (jump to +1 would limit UI because of lackluster cyclical handling).
else if ((this._currentPage == 2 && this._previousPage == 0) || (this._currentPage == 0 && this._previousPage == 1) || (this._currentPage == 1 && this._previousPage == 2)) } else if ((this._currentPage == 2 && this._previousPage == 0) || (this._currentPage == 0 && this._previousPage == 1) || (this._currentPage == 1 && this._previousPage == 2)) {
this._pageController.previousPage(duration: Duration(milliseconds: 200), curve: Curves.linear); this._pageController.previousPage(duration: Duration(milliseconds: 200), curve: Curves.linear);
}
this._previousPage = this._currentPage; this._previousPage = this._currentPage;
} }
......
...@@ -2,14 +2,15 @@ import 'dart:async'; ...@@ -2,14 +2,15 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_map/plugin_api.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong/latlong.dart'; import 'package:latlong/latlong.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/logic/handlers/main_database_handler.dart'; import 'package:logair_application/logic/handlers/main_database_handler.dart';
import 'package:logair_application/logic/handlers/network_handler.dart'; import 'package:logair_application/logic/handlers/network_handler.dart';
import 'package:logair_application/logic/data_header.dart'; import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart'; import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/logic/handlers/position_handler.dart'; import 'package:logair_application/logic/handlers/position_handler.dart';
import 'package:logair_application/utils/enums/pm_symbol.dart'; import 'package:logair_application/utils/enums/pm_symbol.dart';
import 'package:rxdart/rxdart.dart';
/// The [MapDisplayController] is a singleton that handles the mapping of information. /// The [MapDisplayController] is a singleton that handles the mapping of information.
class MapDisplayController { class MapDisplayController {
...@@ -23,7 +24,10 @@ class MapDisplayController { ...@@ -23,7 +24,10 @@ class MapDisplayController {
LatLng _lastPos; LatLng _lastPos;
/// The [FlutterMap]'s zoom level, used to keep centering stable. /// The [FlutterMap]'s zoom level, used to keep centering stable.
double _zoom; double _zoom = 13;
/// Retrieves [FlutterMap]'s current zoom level.
double get zoom => _zoom;
/// The [FlutterMap]s [MapController], kept unique in order to avoid incessant reloading of resources. /// The [FlutterMap]s [MapController], kept unique in order to avoid incessant reloading of resources.
MapController _controller; MapController _controller;
...@@ -34,6 +38,10 @@ class MapDisplayController { ...@@ -34,6 +38,10 @@ class MapDisplayController {
/// [Marker] representing the user's current [Position]. /// [Marker] representing the user's current [Position].
Marker _marker; Marker _marker;
/// Null [BehaviorSubject] tracking when the [FlutterMap] should be redrawn.
BehaviorSubject shouldRedraw = BehaviorSubject();
Future<void> _rebuildMarkers() async { Future<void> _rebuildMarkers() async {
List<DataPacket> packets = await MainDatabaseHandler().getLastPackets(200); List<DataPacket> packets = await MainDatabaseHandler().getLastPackets(200);
packets.forEach((DataPacket packet) { packets.forEach((DataPacket packet) {
...@@ -41,7 +49,7 @@ class MapDisplayController { ...@@ -41,7 +49,7 @@ class MapDisplayController {
case PMSymbol.PM1: case PMSymbol.PM1:
this.addToList(this._currentPos, packet.pm1, 200); this.addToList(this._currentPos, packet.pm1, 200);
break; break;
case PMSymbol.PM1: case PMSymbol.PM2_5:
this.addToList(this._currentPos, packet.pm2_5, 200); this.addToList(this._currentPos, packet.pm2_5, 200);
break; break;
case PMSymbol.PM4: case PMSymbol.PM4:
...@@ -58,17 +66,26 @@ class MapDisplayController { ...@@ -58,17 +66,26 @@ class MapDisplayController {
List<CircleMarker> _pmMarkers = <CircleMarker>[]; List<CircleMarker> _pmMarkers = <CircleMarker>[];
/// [Singleton]() instantiation factory. /// Singleton instantiation factory.
factory MapDisplayController() => _singleton; factory MapDisplayController() => _singleton;
final BehaviorSubject<Position> _positionObserver = PositionHandler().devicePosition;
/// @Getter for [_polylines]. /// @Getter for [_polylines].
List<Polyline> get polylines => this._polylines; List<Polyline> get polylines => this._polylines;
/// @Getter for [_marker]. /// @Getter for [_marker].
Marker get marker => this._marker != null ? this._marker : _buildLocationMarker(LatLng(0, 0)); Marker get marker {
if (this._marker == null) {
LatLng position = this._positionObserver.value == null ? LatLng(0, 0) : LatLng(this._positionObserver.value.latitude, this._positionObserver.value.longitude);
this._marker = _buildLocationMarker(position);
}
return this._marker;
}
/// Object used to calculate the real-life distance of two [Position]s /// Object used to calculate the real-life distance of two [Position]s
final Distance _distance = new Distance(); final Distance _distance = Distance();
List<CircleMarker> get pmMarkers => this._pmMarkers; List<CircleMarker> get pmMarkers => this._pmMarkers;
...@@ -82,31 +99,35 @@ class MapDisplayController { ...@@ -82,31 +99,35 @@ class MapDisplayController {
} }
void addToList(LatLng position, double value, int maxPoints) { void addToList(LatLng position, double value, int maxPoints) {
if (this._pmMarkers.length > maxPoints) if (this._pmMarkers.length > maxPoints) {
this._pmMarkers.removeRange(0, this._pmMarkers.length - 200); this._pmMarkers.removeRange(0, this._pmMarkers.length - 200);
}
if (value != null) if (value != null) {
this._pmMarkers.add(_buildPMMarker(position, value)); this._pmMarkers.add(_buildPMMarker(position, value));
}
} }
/// This function is executed on first initialization of the [MapDisplayController] /// This function is executed on first initialization of the [MapDisplayController]
// TODO: Throttle to 1 in 5 updates for efficiency ?
MapDisplayController._internal() { MapDisplayController._internal() {
/// A new [MapController] is used as the global Controller. /// A new [MapController] is used as the global Controller.
/// This avoids Garbage Collection rules and enables the [FlutterMap] to survive [State] changes, and other such events. /// This avoids Garbage Collection rules and enables the [FlutterMap] to survive [State] changes, and other such events.
this._controller = new MapController(); this._controller = MapController();
/// This function is hooked to the [PositionHandler]s progress, ie. when a new [Position] is acquired (once per second), it is broadcasted across the app and can be listened to. /// This function is hooked to the [PositionHandler]s progress, ie. when a new [Position] is acquired (once per second), it is broadcasted across the app and can be listened to.
// TODO Fix this to allow external data (bounded map data) // TODO Fix this to allow external data (bounded map data)
PositionHandler().getCurrentOrLastPosition().listen((posChanged) { PositionHandler().getCurrentOrLastPosition().listen((posChanged) {
if (posChanged == null) if (posChanged == null) {
return; return;
}
/// If a [BluetoothDevice] is connected, then the [DataHandler] will have a [DataPacket] available. /// If a [BluetoothDevice] is connected, then the [DataHandler] will have a [DataPacket] available.
DataPacket latest = null; DataPacket latest;
/// If the [DataPacket] is older than 5 seconds we invalidate it. /// If the [DataPacket] is older than 5 seconds we invalidate it.
if (latest != null && latest.timestamp <= (DateTime.now().millisecondsSinceEpoch - 5000)) if (latest != null && latest.timestamp <= (DateTime.now().millisecondsSinceEpoch - 5000)) {
latest = null; latest = null;
}
/// Keep track of the last [Position] to draw [Polyline]s. /// Keep track of the last [Position] to draw [Polyline]s.
this._lastPos = this._currentPos; this._lastPos = this._currentPos;
...@@ -115,41 +136,49 @@ class MapDisplayController { ...@@ -115,41 +136,49 @@ class MapDisplayController {
this._currentPos = LatLng(posChanged.latitude, posChanged.longitude); this._currentPos = LatLng(posChanged.latitude, posChanged.longitude);
/// If the user has not acquired manual control, then center the map onto the newer position. /// If the user has not acquired manual control, then center the map onto the newer position.
if (!this._manualMotion && this._controller != null && this._controller.ready) if (!this._manualMotion && this._controller != null && this._controller.ready) {
this._controller.move(this._currentPos, this._zoom); this._controller.move(this._currentPos, this._zoom);
}
/// Move the [Marker] to the current [Position]. /// Move the [Marker] to the current [Position].
// TODO: Use StreamBuilder in Map.
this._marker = this._buildLocationMarker(this._currentPos); this._marker = this._buildLocationMarker(this._currentPos);
if (this._currentPos != this._lastPos) {
this.shouldRedraw.value = null;
}
if ((this._lastPos != null && this._currentPos != null)) {// && this._distance.as(LengthUnit.Meter, this._lastPos, this._currentPos) >= 2)) { if ((this._lastPos != null && this._currentPos != null) && this._distance.as(LengthUnit.Meter, this._lastPos, this._currentPos) >= .5) {
if (this._polylines.length == 0) { if (this._polylines.isEmpty) {
this._polylines.add( this._polylines.add(
Polyline( Polyline(
points: [this._lastPos, this._currentPos], points: [this._lastPos, this._currentPos],
color: Colors.blue color: Colors.blue
) )
); );
} else } else {
this._polylines.last.points.add(this._currentPos); this._polylines.last.points.add(this._currentPos);
}
if (latest != null) { if (latest != null) {
switch (_pmSymbol) { switch (_pmSymbol) {
case PMSymbol.PM1: case PMSymbol.PM1:
if (latest.pm1 != null) if (latest.pm1 != null) {
this.addToList(this._currentPos, latest.pm1, 200); this.addToList(this._currentPos, latest.pm1, 200);
}
break; break;
case PMSymbol.PM2_5: case PMSymbol.PM2_5:
if (latest.pm2_5 != null) if (latest.pm2_5 != null) {
this.addToList(this._currentPos, latest.pm2_5, 200); this.addToList(this._currentPos, latest.pm2_5, 200);
}
break; break;
case PMSymbol.PM4: case PMSymbol.PM4:
if (latest.pm4 != null) if (latest.pm4 != null) {
this.addToList(this._currentPos, latest.pm4, 200); this.addToList(this._currentPos, latest.pm4, 200);
}
break; break;
case PMSymbol.PM10: case PMSymbol.PM10:
if (latest.pm10 != null) if (latest.pm10 != null) {
this.addToList(this._currentPos, latest.pm10, 200); this.addToList(this._currentPos, latest.pm10, 200);
}
break; break;
default: default:
break; break;
...@@ -158,19 +187,24 @@ class MapDisplayController { ...@@ -158,19 +187,24 @@ class MapDisplayController {
} }
}); });
new Timer.periodic(Duration(minutes: 1), (Timer t) { Timer.periodic(Duration(minutes: 1), (Timer t) {
if (this._controller != null && this._controller.bounds != null) { if (this._controller != null && this._controller.bounds != null) {
//print('${this._controller.bounds.southWest}, ${this._controller.bounds.northEast}');
NetworkHandler().getProximityPoints(DataHeader().deviceId, this._controller.bounds); NetworkHandler().getProximityPoints(DataHeader().deviceId, this._controller.bounds);
} }
}); });
} }
@override
MapDisplayController.dispose() {
shouldRedraw.close();
_positionObserver.close();
}
/// Internal static [Singleton]() reference. /// Internal static [Singleton]() reference.
static final MapDisplayController _singleton = new MapDisplayController._internal(); static final MapDisplayController _singleton = MapDisplayController._internal();
/// @Getter for [_currentPos] /// @Getter for [_currentPos]
LatLng currentPosition() => _currentPos != null ? _currentPos : LatLng(0, 0); LatLng currentPosition() => _currentPos ?? LatLng(0, 0);
/// @Getter for [_controller] /// @Getter for [_controller]
MapController controller() => _controller; MapController controller() => _controller;
...@@ -178,8 +212,9 @@ class MapDisplayController { ...@@ -178,8 +212,9 @@ class MapDisplayController {
/// Function called whenever the user interacts with the [FlutterMap], setting the [FlutterMap]'s viewport under their control. /// Function called whenever the user interacts with the [FlutterMap], setting the [FlutterMap]'s viewport under their control.
void setControllerChange(LatLng position, double zoom) { void setControllerChange(LatLng position, double zoom) {
/// Avoid triggering full user control when only the zoom value changes. /// Avoid triggering full user control when only the zoom value changes.
if (this._currentPos != null && this._distance.as(LengthUnit.Meter, position, this._currentPos) >= 10) if (this._currentPos != null && this._distance.as(LengthUnit.Meter, position, this._currentPos) >= 10) {
this._manualMotion = true; this._manualMotion = true;
}
this._zoom = zoom; this._zoom = zoom;
} }
...@@ -189,10 +224,11 @@ class MapDisplayController { ...@@ -189,10 +224,11 @@ class MapDisplayController {
/// Enables the user to have a controlled zoom in, zoom out experience. /// Enables the user to have a controlled zoom in, zoom out experience.
void setControllerZoom(bool zoomIn) { void setControllerZoom(bool zoomIn) {
/// Bound the zoomability. /// Bound the zoomability.
if (zoomIn && this._zoom <= 19) if (zoomIn && this._zoom <= 19) {
this._zoom += 0.5; this._zoom += 0.5;
else if (!zoomIn && this._zoom >= 1) } else if (!zoomIn && this._zoom >= 1) {
this._zoom -= 0.5; this._zoom -= 0.5;
}
this._controller.move(this.controller().center, this._zoom); this._controller.move(this.controller().center, this._zoom);
} }
......
...@@ -12,7 +12,7 @@ class DataHeader { ...@@ -12,7 +12,7 @@ class DataHeader {
_headerSet = false; _headerSet = false;
} }
static final DataHeader _singleton = new DataHeader._internal(); static final DataHeader _singleton = DataHeader._internal();
/// Data contained within the Header packet /// Data contained within the Header packet
List<String> _headerData; List<String> _headerData;
...@@ -64,7 +64,7 @@ class DataHeader { ...@@ -64,7 +64,7 @@ class DataHeader {
Stream<String> deviceIdStream() async* { Stream<String> deviceIdStream() async* {
while(true) { while(true) {
yield this._deviceId; yield this._deviceId ?? '';
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
} }
} }
......
...@@ -9,6 +9,7 @@ import 'package:logair_application/logic/handlers/preference_handler.dart'; ...@@ -9,6 +9,7 @@ import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/utils/enums/bluetooth_connection_status.dart'; import 'package:logair_application/utils/enums/bluetooth_connection_status.dart';
import 'package:logair_application/logic/handlers/data_handler.dart'; import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/utils/enums/preference_keys.dart'; import 'package:logair_application/utils/enums/preference_keys.dart';
import 'package:logair_application/utils/utils.dart';
// TODO Attempt reacquiring device after 5 seconds inactivity. // TODO Attempt reacquiring device after 5 seconds inactivity.
...@@ -24,7 +25,7 @@ class BTLEHandler { ...@@ -24,7 +25,7 @@ class BTLEHandler {
} }
/// Internal static [Singleton]() reference. /// Internal static [Singleton]() reference.
static final BTLEHandler _singleton = new BTLEHandler._internal(); static final BTLEHandler _singleton = BTLEHandler._internal();
/// The referenced [BluetoothCharacteristic]. /// The referenced [BluetoothCharacteristic].
/// Should be singular, unless we plan to broadcast over channels. /// Should be singular, unless we plan to broadcast over channels.
...@@ -68,15 +69,17 @@ class BTLEHandler { ...@@ -68,15 +69,17 @@ class BTLEHandler {
/// Makes the phone connect to the [BluetoothDevice] and initiate [BluetoothService] discovery. /// Makes the phone connect to the [BluetoothDevice] and initiate [BluetoothService] discovery.
Future<void> connect() async { Future<void> connect() async {
/// If the [BluetoothDevice] is connected to this handler. /// If the [BluetoothDevice] is connected to this handler.
if (this._device != null) if (this._device != null) {
await this._asyncConnectionProcess(); await this._asyncConnectionProcess();
}
} }
/// Makes the smartphone disconnect from the [BluetoothDevice]. /// Makes the smartphone disconnect from the [BluetoothDevice].
Future<dynamic> disconnect() => this._device?.let((it) async { Future<dynamic> disconnect() => this._device?.let((it) async {
/// Remove notifications and references to the [BluetoothDevice] or [BluetoothCharacteristic]s. /// Remove notifications and references to the [BluetoothDevice] or [BluetoothCharacteristic]s.
if (_characteristic != null) if (_characteristic != null) {
await _characteristic.setNotifyValue(false); await _characteristic.setNotifyValue(false);
}
_characteristic = null; _characteristic = null;
_device = null; _device = null;
_bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING; _bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING;
...@@ -97,23 +100,22 @@ class BTLEHandler { ...@@ -97,23 +100,22 @@ class BTLEHandler {
final String bluetoothLeCc254xServiceUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__SERVICE_UUID.key); final String bluetoothLeCc254xServiceUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__SERVICE_UUID.key);
final String bluetoothLeCc254xReadUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__CHARACTERISTIC_UUID.key); final String bluetoothLeCc254xReadUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__CHARACTERISTIC_UUID.key);
print('$bluetoothLeCc254xServiceUUID\n$bluetoothLeCc254xReadUUID');
/// Get the first round of [BluetoothService]s and apply filter to desired Service UUID. /// Get the first round of [BluetoothService]s and apply filter to desired Service UUID.
services = await this._device.services.first; services = await this._device.services.first;
services = services.where((s) => s.uuid.toString() == bluetoothLeCc254xServiceUUID).toList(); services = services.where((s) => s.uuid.toString() == bluetoothLeCc254xServiceUUID).toList();
/// If the [BluetoothService] exists. /// If the [BluetoothService] exists.
if (services.length > 0) { if (services.isNotEmpty) {
/// Get the associated [BluetoothCharacteristic]s and apply filter to desired Characteristic UUID. /// Get the associated [BluetoothCharacteristic]s and apply filter to desired Characteristic UUID.
characteristics = services[0].characteristics; characteristics = services[0].characteristics;
characteristics = characteristics.where((c) => c.uuid.toString() == bluetoothLeCc254xReadUUID).toList(); characteristics = characteristics.where((c) => c.uuid.toString() == bluetoothLeCc254xReadUUID).toList();
/// If the [BluetoothCharacteristic] exists. /// If the [BluetoothCharacteristic] exists.
if (characteristics.length > 0) { if (characteristics.isNotEmpty) {
/// Set the [BluetoothCharacteristic] to notify new data. /// Set the [BluetoothCharacteristic] to notify new data.
if (!characteristics[0].isNotifying) if (!characteristics[0].isNotifying) {
await characteristics[0].setNotifyValue(true); await characteristics[0].setNotifyValue(true);
}
_characteristic = characteristics[0]; _characteristic = characteristics[0];
success = true; success = true;
} }
...@@ -133,14 +135,14 @@ class BTLEHandler { ...@@ -133,14 +135,14 @@ class BTLEHandler {
/// Apply a pipe transform to the values that are notified by the [BluetoothCharacteristic]. /// Apply a pipe transform to the values that are notified by the [BluetoothCharacteristic].
_characteristic.value.transform( _characteristic.value.transform(
new StreamTransformer.fromHandlers( StreamTransformer.fromHandlers(
handleData: (List<int> list, EventSink<List<int>> sink) => handleData: (List<int> list, EventSink<List<int>> sink) =>
sink.add((list.isNotEmpty) ? list : [])) sink.add((list.isNotEmpty) ? list : []))
).listen((data) => ).listen((data) =>
/// Handoff the data to the [DataHandler]. /// Handoff the data to the [DataHandler].
DataHandler().addData(data) DataHandler().addData(data)
); );
print("RETURNS"); printDebug('BTHandler: Returned');
success = true; success = true;