dotintent / react-native-ble-plx

React Native BLE library
Apache License 2.0
3.06k stars 513 forks source link

🐛 startDeviceScan errors with "BleManager was destroyed" on android API level 33 (android 13) when published (works fine when running with metro) #1238

Open ArthurRuxton-DY opened 2 months ago

ArthurRuxton-DY commented 2 months ago

Prerequisites

Expected Behavior

startDeviceScan should work as normal

Current Behavior

My app works on ios, android 11, android 14. It also works on android 14 when running with metro (npx react-native run-android) When running the published app, startDeviceScan always errors with "BleManger was destroyed". I've even tried conditionally reinitialising bleManager imediately before device scan: if(!bleManager) bleManager = new BleManager()

Library version

3.2.1

Device

all android 13 (api level 33) devices

Environment info

react-native 0.74.0

Steps to reproduce

attempt startDeviceScan in published app on android 13 (api 33)

Formatted code sample or link to a repository

const callBleScanner = async () => {
    // if no bleManager: reinitialise 
    if(!bleManager){
      bleManager = new BleManager();
      // verify that the ble manager is created. 
      logger(`BLE Manager reinitialized? ${bleManager}`)
    } 

    // to ensure useEffect is triggered by errors: reset 'scanError' state
    setScanError(false);

    // Use a try catch for more robust error catching
    try{
      // Attempt scanning 
      logger("Attempt startDeviceScan:")
      bleManager.startDeviceScan(
        null, 
        // use low latency scan on android for quicker discovery. 
        Platform.OS === "android" ? {scanMode: ScanMode.LowLatency} : null,
        // handle scan error.  
        (error, scannedDevice) => {
          if (error) {
            // stringify the ble error to get all available information. 
            setScanError(true);
            logger(`Bluetooth scanning error: ${JSON.stringify(error)}`)
          }

          if (!scannedDevice) return
          if(!scannedDevice.name) return 
          let nameContainsKeyChars = /(ES-|IS-)/.test(scannedDevice.name)
          if(!nameContainsKeyChars) return
          logger(`DEVICE FOUND: ${scannedDevice.name}`)
          // conditionally update state with new device. 
          setDevicesFound((prevState: Device[]) => {
            if (isOriginalDevice(prevState, scannedDevice)) {
              return [...prevState, scannedDevice];
            } 
            return prevState;
          });
        }
      );
    } catch (error) {
      // this catch always runs on android 13 (api 33) in the published app (not in dev)
      logger(`Failed Bluetooth-state check & scan-start: ${error}`)
      setScanError(true)
    }

  }

Relevant log output

"Failed Bluetooth-state check & scan-start: BleManagerWasDestroyed"

Additional information

No response

intent-kacper-cyranowski commented 1 month ago

Hi @ArthurRuxton-DY

Where do you store your bleManager? According to the documentation it should be initialized once, outside of React.

Aside from that, your code looks fine, so I would need more context to determine what's causing the error.

ArthurRuxton-DY commented 1 month ago

Hi @intent-kacper-cyranowski thanks for your response. I have a custom hook where all blw methods live. Originally my bleManager was initialised at the top of that file, so outside of React. I think Android 13 did not agree with some other aspect of my set up.

In the end the only way to get my implementation to work on all api levels was to initialise the bleManager after permissions had been granted rather than before. This solution actually caused the same bug in IOS, so now I conditionally initialise the bleManger before / after permissions are granted.

for IOS: initialise bleManager > request permissions > check permissions granted > start scanning for Android: request permissions > check permissions granted > initialise bleManager > start scanning