diff --git a/android/src/main/java/com/polidea/flutter_ble_lib/FlutterBleLibPlugin.java b/android/src/main/java/com/polidea/flutter_ble_lib/FlutterBleLibPlugin.java index e2007224..a9d799a2 100644 --- a/android/src/main/java/com/polidea/flutter_ble_lib/FlutterBleLibPlugin.java +++ b/android/src/main/java/com/polidea/flutter_ble_lib/FlutterBleLibPlugin.java @@ -12,8 +12,8 @@ import com.polidea.flutter_ble_lib.delegate.DescriptorsDelegate; import com.polidea.flutter_ble_lib.delegate.DeviceConnectionDelegate; import com.polidea.flutter_ble_lib.delegate.DevicesDelegate; -import com.polidea.flutter_ble_lib.delegate.LogLevelDelegate; import com.polidea.flutter_ble_lib.delegate.DiscoveryDelegate; +import com.polidea.flutter_ble_lib.delegate.LogLevelDelegate; import com.polidea.flutter_ble_lib.delegate.MtuDelegate; import com.polidea.flutter_ble_lib.delegate.RssiDelegate; import com.polidea.flutter_ble_lib.event.AdapterStateStreamHandler; @@ -23,15 +23,11 @@ import com.polidea.flutter_ble_lib.event.ScanningStreamHandler; import com.polidea.multiplatformbleadapter.BleAdapter; import com.polidea.multiplatformbleadapter.BleAdapterFactory; -import com.polidea.multiplatformbleadapter.OnErrorCallback; import com.polidea.multiplatformbleadapter.OnEventCallback; -import com.polidea.multiplatformbleadapter.ScanResult; -import com.polidea.multiplatformbleadapter.errors.BleError; import java.util.LinkedList; import java.util.List; -import androidx.annotation.NonNull; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -88,6 +84,7 @@ private void setupAdapter(Context context) { delegates.add(new CharacteristicsDelegate(bleAdapter, characteristicsMonitorStreamHandler)); delegates.add(new DevicesDelegate(bleAdapter)); delegates.add(new DescriptorsDelegate(bleAdapter)); + scanningStreamHandler.attachAdapter(bleAdapter); } @Override @@ -107,11 +104,8 @@ public void onMethodCall(MethodCall call, Result result) { case MethodName.DESTROY_CLIENT: destroyClient(result); break; - case MethodName.START_DEVICE_SCAN: - startDeviceScan(call, result); - break; case MethodName.STOP_DEVICE_SCAN: - stopDeviceScan(result); + scanningStreamHandler.stopDeviceScan(result); break; case MethodName.CANCEL_TRANSACTION: cancelTransaction(call, result); @@ -140,38 +134,13 @@ public void onEvent(Integer restoreStateIdentifier) { private void destroyClient(Result result) { bleAdapter.destroyClient(); - scanningStreamHandler.onComplete(); + scanningStreamHandler.detachAdapter(); connectionStateStreamHandler.onComplete(); bleAdapter = null; delegates.clear(); result.success(null); } - private void startDeviceScan(@NonNull MethodCall call, Result result) { - List uuids = call.>argument(ArgumentKey.UUIDS); - bleAdapter.startDeviceScan(uuids.toArray(new String[uuids.size()]), - call.argument(ArgumentKey.SCAN_MODE), - call.argument(ArgumentKey.CALLBACK_TYPE), - new OnEventCallback() { - @Override - public void onEvent(ScanResult data) { - scanningStreamHandler.onScanResult(data); - } - }, new OnErrorCallback() { - @Override - public void onError(BleError error) { - scanningStreamHandler.onError(error); - } - }); - result.success(null); - } - - private void stopDeviceScan(Result result) { - bleAdapter.stopDeviceScan(); - scanningStreamHandler.onComplete(); - result.success(null); - } - private void cancelTransaction(MethodCall call, Result result) { bleAdapter.cancelTransaction(call.argument(ArgumentKey.TRANSACTION_ID)); result.success(null); diff --git a/android/src/main/java/com/polidea/flutter_ble_lib/event/ScanningStreamHandler.java b/android/src/main/java/com/polidea/flutter_ble_lib/event/ScanningStreamHandler.java index 0e2fc50c..66f9613d 100644 --- a/android/src/main/java/com/polidea/flutter_ble_lib/event/ScanningStreamHandler.java +++ b/android/src/main/java/com/polidea/flutter_ble_lib/event/ScanningStreamHandler.java @@ -1,47 +1,111 @@ package com.polidea.flutter_ble_lib.event; +import android.util.Log; + +import com.polidea.flutter_ble_lib.constant.ArgumentKey; +import com.polidea.flutter_ble_lib.constant.ChannelName; import com.polidea.flutter_ble_lib.converter.BleErrorJsonConverter; import com.polidea.flutter_ble_lib.converter.ScanResultJsonConverter; +import com.polidea.multiplatformbleadapter.BleAdapter; +import com.polidea.multiplatformbleadapter.OnErrorCallback; +import com.polidea.multiplatformbleadapter.OnEventCallback; import com.polidea.multiplatformbleadapter.ScanResult; import com.polidea.multiplatformbleadapter.errors.BleError; +import com.polidea.multiplatformbleadapter.errors.BleErrorCode; + +import org.json.JSONObject; + +import java.util.List; +import java.util.Map; import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; public class ScanningStreamHandler implements EventChannel.StreamHandler { + /** + * @see MethodCall#argument(java.lang.String) + */ + @SuppressWarnings("unchecked") + public static T argument(Object arguments, String key) { + if (arguments == null) { + return null; + } else if (arguments instanceof Map) { + return (T) ((Map) arguments).get(key); + } else if (arguments instanceof JSONObject) { + return (T) ((JSONObject) arguments).opt(key); + } else { + throw new ClassCastException(); + } + } private EventChannel.EventSink scanResultsSink; - private ScanResultJsonConverter scanResultJsonConverter = new ScanResultJsonConverter(); - private BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); + private final ScanResultJsonConverter scanResultJsonConverter = new ScanResultJsonConverter(); + private final BleErrorJsonConverter bleErrorJsonConverter = new BleErrorJsonConverter(); + private BleAdapter bleAdapter; - @Override - synchronized public void onListen(Object o, EventChannel.EventSink eventSink) { - scanResultsSink = eventSink; + synchronized public void attachAdapter(BleAdapter bleAdapter) { + this.bleAdapter = bleAdapter; } - @Override - synchronized public void onCancel(Object o) { - scanResultsSink = null; + synchronized public void detachAdapter() { + cancelPreviousScanning("detach adapter"); + bleAdapter = null; } - synchronized public void onScanResult(ScanResult scanResult) { + private void cancelPreviousScanning(String reason) { if (scanResultsSink != null) { - scanResultsSink.success(scanResultJsonConverter.toJson(scanResult)); + onError(scanResultsSink, new BleError(BleErrorCode.OperationCancelled, reason, null)); + scanResultsSink = null; } + bleAdapter.stopDeviceScan(); } - synchronized public void onError(BleError error) { - if (scanResultsSink != null) { - scanResultsSink.error( - String.valueOf(error.errorCode.code), - error.reason, - bleErrorJsonConverter.toJson(error)); - scanResultsSink.endOfStream(); - } + synchronized public void stopDeviceScan(MethodChannel.Result result) { + cancelPreviousScanning("stop device scan"); + result.success(null); } - synchronized public void onComplete() { - if (scanResultsSink != null) { - scanResultsSink.endOfStream(); - } + @Override + synchronized public void onListen(Object arguments, final EventChannel.EventSink eventSink) { + Log.d("FlutterBleLibPlugin", "on native side listen: " + ChannelName.SCANNING_EVENTS); + cancelPreviousScanning("Restart the scan"); + scanResultsSink = eventSink; + + List uuids = argument(arguments, ArgumentKey.UUIDS); + int scanMode = argument(arguments, ArgumentKey.SCAN_MODE); + int callbackType = argument(arguments, ArgumentKey.CALLBACK_TYPE); + bleAdapter.startDeviceScan(uuids.toArray(new String[uuids.size()]), scanMode, callbackType, + new OnEventCallback() { + @Override + public void onEvent(ScanResult data) { + onScanResult(eventSink, data); + } + }, + new OnErrorCallback() { + @Override + public void onError(BleError error) { + ScanningStreamHandler.this.onError(eventSink, error); + } + } + ); + } + + @Override + synchronized public void onCancel(Object arguments) { + Log.d("FlutterBleLibPlugin", "on native side cancel: " + ChannelName.SCANNING_EVENTS); + bleAdapter.stopDeviceScan(); + scanResultsSink = null; + } + + private void onScanResult(EventChannel.EventSink eventSink, ScanResult scanResult) { + eventSink.success(scanResultJsonConverter.toJson(scanResult)); + } + + private void onError(EventChannel.EventSink eventSink, BleError error) { + eventSink.error( + String.valueOf(error.errorCode.code), + error.reason, + bleErrorJsonConverter.toJson(error)); } } diff --git a/ios/Classes/Event/ScanningStreamHandler.h b/ios/Classes/Event/ScanningStreamHandler.h index 90ae9724..95a79ca5 100644 --- a/ios/Classes/Event/ScanningStreamHandler.h +++ b/ios/Classes/Event/ScanningStreamHandler.h @@ -1,8 +1,11 @@ #import +@import MultiplatformBleAdapter; @interface ScanningStreamHandler : NSObject +- (void)attachAdatper:(id )adapter; +- (void)detachAdapter; +- (void)stopDeviceScan:(FlutterResult)result; - (void)onScanResult:(NSArray *)scanResult; -- (void)onComplete; @end diff --git a/ios/Classes/Event/ScanningStreamHandler.m b/ios/Classes/Event/ScanningStreamHandler.m index f49f7514..43f4a365 100644 --- a/ios/Classes/Event/ScanningStreamHandler.m +++ b/ios/Classes/Event/ScanningStreamHandler.m @@ -2,13 +2,44 @@ #import "ArgumentHandler.h" #import "JSONStringifier.h" #import "FlutterErrorFactory.h" +#import "ArgumentKey.h" @implementation ScanningStreamHandler { FlutterEventSink scanResultsSink; + id bleAdapter; +} + +- (void) attachAdatper:(id)adapter { + bleAdapter = adapter; +} + +- (void) detachAdapter{ + [self cancelPreviousScanning:@"detach adapter"]; + bleAdapter = nil; +} + +- (void) cancelPreviousScanning:(NSString *)message{ + if (scanResultsSink != nil){ + NSString *errorCode = @"2"; // OperationCancelled + NSDictionary *json = @{@"errorCode": errorCode, @"reason": message}; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + scanResultsSink([FlutterError errorWithCode:errorCode message:message details: jsonString]); + scanResultsSink = nil; + } + [bleAdapter stopDeviceScan]; +} + +- (void)stopDeviceScan:(FlutterResult)result { + @synchronized (self) { + [self cancelPreviousScanning:@"stop device scan"]; + result(nil); + } } - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { @synchronized (self) { + [bleAdapter stopDeviceScan]; scanResultsSink = nil; return nil; } @@ -16,7 +47,11 @@ - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { @synchronized (self) { + [self cancelPreviousScanning:@"Restart the scan"]; scanResultsSink = events; + + NSArray* expectedArguments = [NSArray arrayWithObjects:ARGUMENT_KEY_ALLOW_DUPLICATES, nil]; + [bleAdapter startDeviceScan:[ArgumentHandler stringArrayOrNil:arguments[ARGUMENT_KEY_UUIDS]] options:[ArgumentHandler dictionaryOrNil:expectedArguments in:arguments]]; return nil; } } @@ -27,27 +62,17 @@ - (void)onScanResult:(NSArray *)scanResult { if (!(scanResult.count == 2 && (scanResult[0] == [NSNull null] || (scanResult[1] == [NSNull null] && [scanResult[0] isKindOfClass:NSString.class])))) { scanResultsSink([FlutterError errorWithCode:@"-1" message:@"Invalid scanResult format." details:nil]); - [self onComplete]; } else { if (scanResult[0] == [NSNull null]) { scanResultsSink([JSONStringifier jsonStringFromJSONObject:scanResult[1]]); } else { scanResultsSink([FlutterErrorFactory flutterErrorFromJSONString:scanResult[0]]); - [self onComplete]; } } } } } -- (void)onComplete { - @synchronized (self) { - if (scanResultsSink != nil) { - scanResultsSink(FlutterEndOfEventStream); - } - } -} - - (nullable NSString *)validStringOrNil:(id)argument { if (argument != nil && (NSNull *)argument != [NSNull null] && [argument isKindOfClass:[NSString class]]) { return (NSString *)argument; diff --git a/ios/Classes/FlutterBleLibPlugin.m b/ios/Classes/FlutterBleLibPlugin.m index f8cfea44..b582a5bb 100644 --- a/ios/Classes/FlutterBleLibPlugin.m +++ b/ios/Classes/FlutterBleLibPlugin.m @@ -77,10 +77,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self disable:call result:result]; } else if ([METHOD_NAME_GET_STATE isEqualToString:call.method]) { [self state:call result:result]; - } else if ([METHOD_NAME_START_DEVICE_SCAN isEqualToString:call.method]) { - [self startDeviceScan:call result:result]; } else if ([METHOD_NAME_STOP_DEVICE_SCAN isEqualToString:call.method]) { - [self stopDeviceScan:result]; + [self.scanningStreamHandler stopDeviceScan:result]; } else if ([METHOD_NAME_CONNECT_TO_DEVICE isEqualToString:call.method]) { [self connectToDevice:call result:result]; } else if ([METHOD_NAME_CANCEL_CONNECTION isEqualToString:call.method]) { @@ -162,10 +160,12 @@ - (void)createClient:(FlutterMethodCall *)call result:(FlutterResult)result { _adapter = [BleAdapterFactory getNewAdapterWithQueue:dispatch_get_main_queue() restoreIdentifierKey:[ArgumentHandler stringOrNil:call.arguments[ARGUMENT_KEY_RESTORE_STATE_IDENTIFIER]]]; _adapter.delegate = self; + [self.scanningStreamHandler attachAdatper:_adapter]; result(nil); } - (void)destroyClient { + [self.scanningStreamHandler detachAdapter]; [_adapter invalidate]; _adapter = nil; } @@ -193,21 +193,6 @@ - (void)state:(FlutterMethodCall *)call result:(FlutterResult)result { reject:[self rejectForFlutterResult:result]]; } -// MARK: - MBA Methods - Scanning - -- (void)startDeviceScan:(FlutterMethodCall *)call result:(FlutterResult)result { - NSArray* expectedArguments = [NSArray arrayWithObjects:ARGUMENT_KEY_ALLOW_DUPLICATES, nil]; - [_adapter startDeviceScan:[ArgumentHandler stringArrayOrNil:call.arguments[ARGUMENT_KEY_UUIDS]] - options:[ArgumentHandler dictionaryOrNil:expectedArguments in:call.arguments]]; - result(nil); -} - -- (void)stopDeviceScan:(FlutterResult)result { - [_adapter stopDeviceScan]; - [self.scanningStreamHandler onComplete]; - result(nil); -} - // MARK: - MBA Methods - Connection - (void)connectToDevice:(FlutterMethodCall *)call result:(FlutterResult)result { diff --git a/lib/src/bridge/scanning_mixin.dart b/lib/src/bridge/scanning_mixin.dart index d05a9809..3de25510 100644 --- a/lib/src/bridge/scanning_mixin.dart +++ b/lib/src/bridge/scanning_mixin.dart @@ -1,11 +1,26 @@ part of _internal; mixin ScanningMixin on FlutterBLE { - Stream _scanEvents; + EventChannel _eventChannel; - void _prepareScanEventsStream() { - _scanEvents = const EventChannel(ChannelName.scanningEvents) - .receiveBroadcastStream() + EventChannel get eventChannel => + _eventChannel ??= const EventChannel(ChannelName.scanningEvents); + + Stream startDeviceScan( + int scanMode, + int callbackType, + List uuids, + bool allowDuplicates, + ) { + return eventChannel + .receiveBroadcastStream( + { + ArgumentName.scanMode: scanMode, + ArgumentName.callbackType: callbackType, + ArgumentName.uuids: uuids, + ArgumentName.allowDuplicates: allowDuplicates, + }, + ) .handleError( (errorJson) => throw BleError.fromJson(jsonDecode(errorJson.details)), test: (error) => error is PlatformException, @@ -16,39 +31,7 @@ mixin ScanningMixin on FlutterBLE { ); } - Stream startDeviceScan( - int scanMode, - int callbackType, - List uuids, - bool allowDuplicates, - ) { - if (_scanEvents == null) { - _prepareScanEventsStream(); - } - - StreamController streamController = StreamController.broadcast( - onListen: () => _methodChannel.invokeMethod( - MethodName.startDeviceScan, - { - ArgumentName.scanMode: scanMode, - ArgumentName.callbackType: callbackType, - ArgumentName.uuids: uuids, - ArgumentName.allowDuplicates: allowDuplicates, - }, - ), - onCancel: () => stopDeviceScan(), - ); - - streamController - .addStream(_scanEvents, cancelOnError: true) - .then((_) => streamController?.close()); - - return streamController.stream; - } - Future stopDeviceScan() async { await _methodChannel.invokeMethod(MethodName.stopDeviceScan); - _scanEvents = null; - return; } }