flutter_reactive_ble icon indicating copy to clipboard operation
flutter_reactive_ble copied to clipboard

Calling cancel on ConnectionStateUpdate stream results in PlatformException

Open scottsidekick opened this issue 3 years ago • 8 comments

I am connecting to a device as follows....

        var stream = _ble.connectToAdvertisingDevice(
          id: device.id,
          withServices: [ ],
          prescanDuration: const Duration(seconds: 20),
          connectionTimeout: const Duration(seconds:  20),
        );
        
        _deviceSubscription = stream.listen(
          _connectionStateUpdateListener,
          cancelOnError: true
        ); 

Then later on I disconnect as follows...

    if (_scanSubscription != null)
      await _scanSubscription.cancel();
    if (_deviceSubscription != null)
      await _deviceSubscription.cancel();
    if (_readCharSubscription != null)
      await _readCharSubscription.cancel();

After the deviceSubscription is cancelled, a platform exception is thrown.

Exception has occurred.
PlatformException (PlatformException(flutter_reactive_ble.Central.(unknown context at $1093a3cf0).Failure:1, The operation couldn’t be completed. (flutter_reactive_ble.Central.(unknown context at $1093a3cf0).Failure error 1.), {}, null))

scottsidekick avatar Feb 11 '21 16:02 scottsidekick

You are mentioning some _readCharSubscription. I suppose it's a subscription to a stream returned by subscribeToCharacteristic(·) (because just reading does not involve streams). If this is correct, you are getting an exception in this stream, because it is not possible to listen to characteristic's updates when the connection is terminated (intentionally or not, doesn't matter).

When you are working with IO you should be prepared to handle failures.

werediver avatar Feb 11 '21 16:02 werediver

So is there a suggested way to disconnect? I have changed the order to call cancel on _readCharSubscription first, and the exception is still thrown.

Should I just never ever call cancel on a subscription to a characteristic?

EDIT: I am pretty sure there is a bug here, as the docs, it should be safe to disconnect from the device once the characteristic subscription is disconnected.

scottsidekick avatar Feb 11 '21 16:02 scottsidekick

If you make sure that you close all other subscriptions related to a particular device before you disconnect (which means await on cancel() calls), you shouldn't get exceptions due to loss of connection (introduce a synthetic delay of about a couple hundreds milliseconds after closing everything and before disconnecting, if awaiting is not enough [though, that would be a workaround and a bug in the library, that is worth reporting]).

But note, that the connection doesn't only get terminated, because you close it. It gets terminated, because a peripheral goes out of range or powers off or due to heavy RF interference, etc. This is why you should be prepared for failures when performing IO operations. There will be failures.

If there is a bug, please provide enough details so that we can understand and investigate it. Do not disregard the issue template, because it may answer some of the questions we would anyway ask you. Right now I do not see anything ~criminal~ unexpected in the described behavior (as far as I can tell from the details provided).

At the very least, you should clarify, in which exactly stream the exception occurs, because that is very important (and for that you would need to add error handlers to all the streams, which would be at least half of the solution to your issue already).

werediver avatar Feb 11 '21 17:02 werediver

If I add a delay it works...

    if (_readCharSubscription != null)
      await _readCharSubscription.cancel();
    await Future.delayed(Duration(seconds: 5));
    if (_deviceSubscription != null)
      await _deviceSubscription.cancel();

If I remove a delay it throws an exception....

    if (_readCharSubscription != null)
      await _readCharSubscription.cancel();
    if (_deviceSubscription != null)
      await _deviceSubscription.cancel();

Based on your previous comments....this is a bug? Do you want this submitted as a fresh bug using the template?

scottsidekick avatar Feb 11 '21 17:02 scottsidekick

Thank you for doing the additional tests. I'll reopen this ticket, but it would help if you add this detail about the delay into the original post.

This seems to be related to a discussion in #157. As it pops up the second time, it may be worth reworking / fixing the disconnect operation to complete when the connection has actually been terminated.

CC @safield (you may be interested in following this ticket)

werediver avatar Feb 11 '21 17:02 werediver

"At the very least, you should clarify, in which exactly stream the exception occurs, because that is very important".

I can confirm it is the _readCharSubscription stream that is throwing the PlatformException.

scottsidekick avatar Feb 11 '21 17:02 scottsidekick

Please, check the documentation on Stream.listen(⋯).

You cannot use try-catch around a call to Stream.listen(⋯) to catch errors in a stream, because the call completes before events start to come into onData: and onError: handlers.

werediver avatar Feb 11 '21 17:02 werediver

I can confirm that the error is thrown from the stream with subscription _readCharSubscription.

scottsidekick avatar Feb 17 '21 19:02 scottsidekick