Commit 5c2f54e
Changed files (2)
app
lib
bluetooth
test
bluetooth
app/lib/bluetooth/device_scan_cubit.dart
@@ -2,6 +2,7 @@ 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:blood_pressure_app/bluetooth/logging.dart';
import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
@@ -39,13 +40,10 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
final FlutterBluePlusMockable _flutterBluePlus;
late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
- late StreamSubscription<bool> _isScanningSubscription;
- bool _isScanning = false;
@override
Future<void> close() async {
await _scanResultsSubscription.cancel();
- await _isScanningSubscription.cancel();
await super.close();
}
@@ -65,12 +63,12 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
} catch (e) {
_onScanError(e);
}
- _isScanningSubscription = _flutterBluePlus.isScanning
- .listen(_onIsScanningChanged);
}
void _onScanResult(List<ScanResult> devices) {
- assert(_isScanning);
+ assert(_flutterBluePlus.isScanningNow);
+ // No need to check whether the devices really support the searched
+ // characteristic as users have to select their device anyways.
if(state is DeviceSelected) return;
final preferred = devices.firstWhereOrNull((dev) =>
settings.knownBleDev.contains(dev.device.advName));
@@ -86,12 +84,7 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
}
void _onScanError(Object error) {
- // TODO
- }
-
- void _onIsScanningChanged(bool isScanning) {
- _isScanning = isScanning;
- // TODO: consider restarting
+ Log.err('Starting device scan failed');
}
/// Mark a new device as known and switch to selected device state asap.
@@ -103,16 +96,18 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
_onScanError(e);
return;
}
- assert(!_isScanning);
+ assert(!_flutterBluePlus.isScanningNow);
emit(DeviceSelected(device));
- settings.knownBleDev.add(device.advName); // TODO: does this work?
+ final List<String> list = settings.knownBleDev.toList();
+ list.add(device.advName);
+ settings.knownBleDev = list;
}
/// Remove all known devices and start scanning again.
Future<void> clearKnownDevices() async {
settings.knownBleDev = [];
emit(DeviceListLoading());
- if (!_isScanning) await _startScanning();
+ if (!_flutterBluePlus.isScanningNow) await _startScanning();
}
}
app/test/bluetooth/device_scan_cubit_test.dart
@@ -0,0 +1,110 @@
+import 'dart:async';
+
+import 'package:blood_pressure_app/bluetooth/device_scan_cubit.dart';
+import 'package:blood_pressure_app/bluetooth/flutter_blue_plus_mockable.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.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<BluetoothDevice>(),
+ MockSpec<BluetoothService>(),
+ MockSpec<AdvertisementData>(),
+ MockSpec<FlutterBluePlusMockable>(),
+ MockSpec<ScanResult>(),
+])
+import 'device_scan_cubit_test.mocks.dart';
+
+void main() {
+ test('finds and connects to devices', () async {
+ final StreamController<List<ScanResult>> mockResults = StreamController.broadcast();
+ final settings = Settings();
+
+ final flutterBluePlus = MockFlutterBluePlusMockable();
+ when(flutterBluePlus.startScan(
+ withServices: [Guid('1810')]
+ )).thenAnswer((_) async {
+ when(flutterBluePlus.isScanningNow).thenReturn(true);
+ });
+ when(flutterBluePlus.stopScan()).thenAnswer((_) async {
+ when(flutterBluePlus.isScanningNow).thenReturn(false);
+ });
+ when(flutterBluePlus.scanResults).thenAnswer((_) => mockResults.stream);
+ final cubit = DeviceScanCubit(
+ service: Guid('1810'),
+ settings: settings,
+ flutterBluePlus: flutterBluePlus
+ );
+ expect(cubit.state, isA<DeviceListLoading>());
+
+ final wrongRes0 = MockScanResult();
+ final wrongDev0 = MockBluetoothDevice();
+ final wrongRes1 = MockScanResult();
+ final wrongDev1 = MockBluetoothDevice();
+ when(wrongDev0.advName).thenReturn('wrongDev0');
+ when(wrongRes0.device).thenReturn(wrongDev0);
+ when(wrongDev1.advName).thenReturn('wrongDev1');
+ when(wrongRes1.device).thenReturn(wrongDev1);
+ mockResults.sink.add([wrongRes0]);
+ await expectLater(cubit.stream, emits(isA<SingleDeviceAvailable>()));
+
+ mockResults.sink.add([wrongRes0, wrongRes1]);
+ await expectLater(cubit.stream, emits(isA<DeviceListAvailable>()));
+
+ final dev = MockBluetoothDevice();
+ when(dev.advName).thenReturn('testDev');
+ final res = MockScanResult();
+ when(res.device).thenReturn(dev);
+
+ mockResults.sink.add([res]);
+ await expectLater(cubit.stream, emits(isA<SingleDeviceAvailable>()
+ .having((r) => r.device.device, 'device', dev)));
+
+ expect(settings.knownBleDev, isEmpty);
+ await cubit.acceptDevice(dev);
+ expect(settings.knownBleDev, contains('testDev'));
+ // state should be set as we await above
+ await expectLater(cubit.state, isA<DeviceSelected>()
+ .having((s) => s.device, 'device', dev));
+ });
+ test('recognizes devices', () async {
+ final StreamController<List<ScanResult>> mockResults = StreamController.broadcast();
+ final settings = Settings(
+ knownBleDev: ['testDev']
+ );
+
+ final flutterBluePlus = MockFlutterBluePlusMockable();
+ when(flutterBluePlus.startScan(
+ withServices: [Guid('1810')]
+ )).thenAnswer((_) async {
+ when(flutterBluePlus.isScanningNow).thenReturn(true);
+ });
+ when(flutterBluePlus.stopScan()).thenAnswer((_) async {
+ when(flutterBluePlus.isScanningNow).thenReturn(false);
+ });
+ when(flutterBluePlus.scanResults).thenAnswer((_) => mockResults.stream);
+ final cubit = DeviceScanCubit(
+ service: Guid('1810'),
+ settings: settings,
+ flutterBluePlus: flutterBluePlus
+ );
+ expect(cubit.state, isA<DeviceListLoading>());
+
+ final wrongRes0 = MockScanResult();
+ final wrongDev0 = MockBluetoothDevice();
+ when(wrongDev0.advName).thenReturn('wrongDev0');
+ when(wrongRes0.device).thenReturn(wrongDev0);
+ mockResults.sink.add([wrongRes0]);
+ await expectLater(cubit.stream, emits(isA<SingleDeviceAvailable>()));
+
+ final dev = MockBluetoothDevice();
+ when(dev.advName).thenReturn('testDev');
+ final res = MockScanResult();
+ when(res.device).thenReturn(dev);
+ mockResults.sink.add([wrongRes0, res]);
+ await expectLater(cubit.stream, emits(isA<DeviceSelected>()
+ .having((s) => s.device, 'device', dev)));
+ });
+}