bluetooth_low_energy icon indicating copy to clipboard operation
bluetooth_low_energy copied to clipboard

Cannot access value of empty optional (Windows)

Open ddomnik opened this issue 10 months ago • 7 comments

Hey, I am developing on windows, and notice a crash when a BLE device disconnects unexpectedly.

How to reproduce:

  1. Connect to BLE device.
  2. Perform hardware reset on BLE device / turn off BLE device.

image

[ERROR:flutter/shell/common/shell.cc(1038)] The 'dev.flutter.pigeon.bluetooth_low_energy_windows.MyCentralManagerFlutterApi.onConnectionStateChanged' channel sent a message from native to Flutter on a non-platform thread. Platform channel messages must be sent on the platform thread. Failure to do so may result in data loss or crashes, and must be fixed in the plugin or application code creating that channel.
See https://docs.flutter.dev/platform-integration/platform-channels#channels-and-platform-threading for more information.
flutter: connectionStateChangedSubscription
Lost connection to device.

Exited.

my code:


class BledSign {


  late final ValueNotifier<BluetoothLowEnergyState> state;
  late final ValueNotifier<bool> discovering;
  late final ValueNotifier<List<DiscoveredEventArgs>> discoveredEventArgs;
  late final StreamSubscription stateChangedSubscription;
  late final StreamSubscription discoveredSubscription;

  late final ValueNotifier<Peripheral?> connectedDevice;
  late final ValueNotifier<String> connectionDeviceName;
  late final ValueNotifier<bool> connectionState;
  late final DiscoveredEventArgs eventArgs;
  late final ValueNotifier<List<GattService>> services;
  late final ValueNotifier<List<GattCharacteristic>> characteristics;
  late final ValueNotifier<GattService?> service;
  late final ValueNotifier<GattCharacteristic?> characteristic;
  late final ValueNotifier<GattCharacteristicWriteType> writeType;
  late final TextEditingController writeController;
  late final StreamSubscription connectionStateChangedSubscription;
  late final StreamSubscription characteristicNotifiedSubscription;

  BledSign._private() {
    state = ValueNotifier(BluetoothLowEnergyState.unknown);
    discovering = ValueNotifier(false);
    discoveredEventArgs = ValueNotifier([]);

    connectedDevice = ValueNotifier(null);
    connectionDeviceName = ValueNotifier("");
    connectionState = ValueNotifier(false);
    services = ValueNotifier([]);
    characteristics = ValueNotifier([]);
    service = ValueNotifier(null);
    characteristic = ValueNotifier(null);
    writeType = ValueNotifier(GattCharacteristicWriteType.withResponse);
    writeController = TextEditingController();

    stateChangedSubscription = CentralManager.instance.stateChanged.listen(
      (eventArgs) {
        print("stateChangedSubscription");

        state.value = eventArgs.state;
        print('Change in stateChangedSubscription: $state.value');
      },
      onError: (error) {
        // Handle errors
        print('Error in stateChangedSubscription: $error');
      },
      onDone: () {
        // Handle stream closure
        print('stateChangedSubscription stream closed');
      },
    );

    discoveredSubscription = CentralManager.instance.discovered.listen(
      (eventArgs) {
        print("discoveredSubscription");

        final items = discoveredEventArgs.value;

        bool isNewEntry = true;
        int indexToUpdate = -1;

        // Check if the device is already in the list
        for (int i = 0; i < items.length; i++) {
          if (items[i].peripheral == eventArgs.peripheral) {
            isNewEntry = false;
            indexToUpdate = i;
            break;
          }
        }

        if (isNewEntry) {
          // Add a new entry if the device is not in the list
          List<DiscoveredEventArgs> updatedItems = List.from(items);
          updatedItems.add(eventArgs);
          discoveredEventArgs.value = updatedItems;

          print("UPDATE");
        } else {
          // Update the existing entry if the device is already in the list
          List<DiscoveredEventArgs> updatedItems = List.from(items);
          updatedItems[indexToUpdate] = eventArgs;
          discoveredEventArgs.value = updatedItems;
        }
      },
    );

    connectionStateChangedSubscription = CentralManager.instance.connectionStateChanged.listen(
      (eventArgs) async {
        print("connectionStateChangedSubscription");


        services.value = await CentralManager.instance.discoverGATT(eventArgs.peripheral);

        final connectionState = eventArgs.connectionState;
        this.connectionState.value = connectionState;
        if (!connectionState) {
          connectionDeviceName.value = "";
          services.value = [];
          characteristics.value = [];
          service.value = null;
          characteristic.value = null;
          connectedDevice.value = null;
          print("connectedDevice null");
        } else {
          discoveredEventArgs.value.forEach((device) {
            if (device.peripheral.uuid == eventArgs.peripheral.uuid) {
              connectionDeviceName.value = device.advertisement.name!;
            }
          });
        }
      },
    );

    characteristicNotifiedSubscription = CentralManager.instance.characteristicNotified.listen(
      (eventArgs) {},
    );

    print("BLED SIGN INSTANCE CREATED");
  }

  // Static private instance variable
  static BledSign? _instance;

  // Static method to access the instance
  static BledSign get instance {
    // Initialize instance if null
    _instance ??= BledSign._private();
    return _instance!;
  }

  init() async {
    hierarchicalLoggingEnabled = true;
    CentralManager.instance.logLevel = Level.ALL;

    await CentralManager.instance.setUp();

    state.value = await CentralManager.instance.getState();

    if (kDebugMode) {
      print('BLED SIGN INSTANCE Initialized ${state.value}');
    }
  }

  Future<void> startDiscovery() async {
    print("START DISCOVERY");
    //discoveredEventArgs.value = [];
    await CentralManager.instance.stopDiscovery();
    await CentralManager.instance.startDiscovery();
    discovering.value = true;

    Future.delayed(Duration(seconds: 5), stopDiscovery);
  }

  Future<void> stopDiscovery() async {
    print("STOP DISCOVERY");
    await CentralManager.instance.stopDiscovery();
    discovering.value = false;
  }

  Future<bool> connect(Peripheral peripheral) async {
    print("Connect to: ${peripheral.uuid}");
    try {
      await CentralManager.instance.connect(peripheral);
      connectedDevice.value = peripheral;
      return true;
    } on Exception catch (_err) {
      print('Connect failed: $_err');
      return false;
    }
  }

  Future<bool> disconnect(Peripheral? peripheral) async {
    connectedDevice.value = null;
    if (peripheral != null) {
      print("Disconnect");
      try {
        await CentralManager.instance.disconnect(peripheral);
        return true;
      } on Exception catch (_err) {
        print('Disconnect failed: $_err');
        return false;
      }
    }
    return false;
  }
  cleanup() {
    stateChangedSubscription.cancel();
    discoveredSubscription.cancel();
    state.dispose();
    discovering.dispose();
    discoveredEventArgs.dispose();

    connectionStateChangedSubscription.cancel();
    characteristicNotifiedSubscription.cancel();
    connectionState.dispose();
    services.dispose();
    characteristics.dispose();
    service.dispose();
    characteristic.dispose();
    writeType.dispose();
    writeController.dispose();
  }
}

ddomnik avatar Apr 21 '24 14:04 ddomnik