Prerequisites
Bug Description
When using the monitorCharacteristicForDevice() method on Android, if the peripheral device disconnects unexpectedly, the application crashes instead of propagating the disconnection error to the monitoring listener callback. The expected behavior is for the listener (error: BleError | null, characteristic: Characteristic | null) => void to be invoked with a BleError object.
The crash is caused by a java.lang.NullPointerException in the native Android code, as the Promise.reject method is called with a null error code.
Expected Behavior
The listener for monitorCharacteristicForDevice() should be called with an error object detailing the disconnection, and the app should continue running without crashing.
Current Behavior
The application crashes with a FATAL EXCEPTION. The logs indicate a java.lang.NullPointerException because Promise.reject was called with a null code parameter.
Library version
3.5.0
Device
One Plus 12, Android 15
Environment info
- React Native: v0.81
- react-native-ble-plx: v3.5.0
- Platform: Android 15
Steps to reproduce
- Connect to a BLE peripheral device.
- Start monitoring a characteristic using
device.monitorCharacteristicForDevice(...).
- Force the device to disconnect (e.g., by turning the peripheral off or moving it out of range).
- Observe the application crash.
Proposed Solution / Workaround
The issue appears to be in the onError callback within the monitorCharacteristicForDevice implementation in BlePlxModule.java. The safePromise is rejected with null as the first argument.
A temporary fix is to modify the native file to provide a valid error code string.
File: /node_modules/react-native-ble-plx/android/src/main/java/com/bleplx/BlePlxModule.java
line number: 791
bleAdapter.monitorCharacteristicForDevice(
deviceId, serviceUUID, characteristicUUID, transactionId, subscriptionType,
new OnEventCallback<Characteristic>() {
@Override
public void onEvent(Characteristic data) {
WritableArray jsResult = Arguments.createArray();
jsResult.pushNull();
jsResult.pushMap(characteristicConverter.toJSObject(data));
jsResult.pushString(transactionId);
sendEvent(Event.ReadEvent, jsResult);
}
}, new OnErrorCallback() {
@Override
public void onError(BleError error) {
- safePromise.reject(null, errorConverter.toJs(error));
+ safePromise.reject(error.errorCode.name(), errorConverter.toJs(error));
}
}
);
Relevant log output
2025-09-04 15:53:51.086 17235-733 AndroidRuntime com.example.dev E FATAL EXCEPTION: RxComputationThreadPool-3 (Ask Gemini)
Process: com.example.dev, PID: 17235
io.reactivex.exceptions.CompositeException: 2 exceptions occurred.
at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:82)
at io.reactivex.internal.operators.flowable.FlowableDoOnEach$DoOnEachSubscriber.onError(FlowableDoOnEach.java:111)
at io.reactivex.internal.operators.flowable.FlowableDoOnLifecycle$SubscriptionLambdaSubscriber.onError(FlowableDoOnLifecycle.java:85)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:399)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:348)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651)
at java.lang.Thread.run(Thread.java:1119)
ComposedException 1 :
com.polidea.rxandroidble2.exceptions.BleDisconnectedException: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 8 (GATT_INSUF_AUTHORIZATION or GATT_CONN_TIMEOUT)
at com.polidea.rxandroidble2.internal.connection.RxBleGattCallback$2.onConnectionStateChange(RxBleGattCallback.java:81)
at android.bluetooth.BluetoothGatt$1$4.run(BluetoothGatt.java:390)
at android.bluetooth.BluetoothGatt.runOrQueueCallback(BluetoothGatt.java:1081)
at android.bluetooth.BluetoothGatt.-$$Nest$mrunOrQueueCallback(Unknown Source:0)
at android.bluetooth.BluetoothGatt$1.onClientConnectionState(BluetoothGatt.java:384)
at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:209)
at android.os.Binder.execTransactInternal(Binder.java:1523)
at android.os.Binder.execTransact(Binder.java:1457)
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method com.facebook.react.bridge.PromiseImpl.reject, parameter code
at com.facebook.react.bridge.PromiseImpl.reject(Unknown Source:2)
at com.bleplx.utils.SafePromise.reject(SafePromise.java:25)
at com.bleplx.BlePlxModule$41.onError(BlePlxModule.java:791)
at com.bleplx.adapter.utils.SafeExecutor.error(SafeExecutor.java:30)
at com.bleplx.adapter.BleModule.lambda$safeMonitorCharacteristicForDevice$45(BleModule.java:1485)
at com.bleplx.adapter.BleModule.$r8$lambda$JVuqIGnSfaxzZFLoyHm91xQUhdI(Unknown Source:0)
at com.bleplx.adapter.BleModule$$ExternalSyntheticLambda3.accept(D8$$SyntheticClass:0)
at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:79)
at io.reactivex.internal.operators.flowable.FlowableDoOnEach$DoOnEachSubscriber.onError(FlowableDoOnEach.java:111)
at io.reactivex.internal.operators.flowable.FlowableDoOnLifecycle$SubscriptionLambdaSubscriber.onError(FlowableDoOnLifecycle.java:85)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:399)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:317)
2025-09-04 15:53:51.086 17235-733 AndroidRuntime com.example.dev E at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:348) (Ask Gemini)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651)
at java.lang.Thread.run(Thread.java:1119)
ComposedException 2 :
java.lang.NullPointerException: Parameter specified as non-null is null: method com.facebook.react.bridge.PromiseImpl.reject, parameter code
at com.facebook.react.bridge.PromiseImpl.reject(Unknown Source:2)
at com.bleplx.utils.SafePromise.reject(SafePromise.java:25)
at com.bleplx.BlePlxModule$41.onError(BlePlxModule.java:791)
at com.bleplx.adapter.utils.SafeExecutor.error(SafeExecutor.java:30)
at com.bleplx.adapter.BleModule.lambda$safeMonitorCharacteristicForDevice$45(BleModule.java:1485)
at com.bleplx.adapter.BleModule.$r8$lambda$JVuqIGnSfaxzZFLoyHm91xQUhdI(Unknown Source:0)
at com.bleplx.adapter.BleModule$$ExternalSyntheticLambda3.accept(D8$$SyntheticClass:0)
at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:79)
at io.reactivex.internal.operators.flowable.FlowableDoOnEach$DoOnEachSubscriber.onError(FlowableDoOnEach.java:111)
at io.reactivex.internal.operators.flowable.FlowableDoOnLifecycle$SubscriptionLambdaSubscriber.onError(FlowableDoOnLifecycle.java:85)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:399)
at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:348)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651)
at java.lang.Thread.run(Thread.java:1119)
2025-09-04 15:53:51.088 3181-5341 OplusAppStartupMonitor system_server D notifyUnstableAppInfo: Bundle[{unstableTime=1756981431088, reason=crash, userId=0, exceptionMsg=Parameter specified as non-null is null: method com.facebook.react.bridge.PromiseImpl.reject, parameter code, exceptionClass=java.lang.NullPointerException, app_channel_type=unstable, packageName=com.example.dev, unstable_restrict_switch=true}]
2025-09-04 15:53:51.090 3181-5676 OplusEapMa...sendEvent: system_server W com.example.dev happenedcrash
2025-09-04 15:53:51.101 3181-5676 ActivityTaskManager system_server W Force finishing activity com.example.dev/com.nilesecure.MainActivity
Prerequisites
Bug Description
When using the
monitorCharacteristicForDevice()method on Android, if the peripheral device disconnects unexpectedly, the application crashes instead of propagating the disconnection error to the monitoring listener callback. The expected behavior is for the listener (error: BleError | null, characteristic: Characteristic | null) => void to be invoked with a BleError object.The crash is caused by a java.lang.NullPointerException in the native Android code, as the Promise.reject method is called with a null error code.
Expected Behavior
The listener for monitorCharacteristicForDevice() should be called with an error object detailing the disconnection, and the app should continue running without crashing.
Current Behavior
The application crashes with a FATAL EXCEPTION. The logs indicate a java.lang.NullPointerException because Promise.reject was called with a null code parameter.
Library version
3.5.0
Device
One Plus 12, Android 15
Environment info
Steps to reproduce
device.monitorCharacteristicForDevice(...).Proposed Solution / Workaround
The issue appears to be in the onError callback within the monitorCharacteristicForDevice implementation in BlePlxModule.java. The safePromise is rejected with null as the first argument.
A temporary fix is to modify the native file to provide a valid error code string.
line number: 791
bleAdapter.monitorCharacteristicForDevice( deviceId, serviceUUID, characteristicUUID, transactionId, subscriptionType, new OnEventCallback<Characteristic>() { @Override public void onEvent(Characteristic data) { WritableArray jsResult = Arguments.createArray(); jsResult.pushNull(); jsResult.pushMap(characteristicConverter.toJSObject(data)); jsResult.pushString(transactionId); sendEvent(Event.ReadEvent, jsResult); } }, new OnErrorCallback() { @Override public void onError(BleError error) { - safePromise.reject(null, errorConverter.toJs(error)); + safePromise.reject(error.errorCode.name(), errorConverter.toJs(error)); } } );Relevant log output
2025-09-04 15:53:51.086 17235-733 AndroidRuntime com.example.dev E FATAL EXCEPTION: RxComputationThreadPool-3 (Ask Gemini) Process: com.example.dev, PID: 17235 io.reactivex.exceptions.CompositeException: 2 exceptions occurred. at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:82) at io.reactivex.internal.operators.flowable.FlowableDoOnEach$DoOnEachSubscriber.onError(FlowableDoOnEach.java:111) at io.reactivex.internal.operators.flowable.FlowableDoOnLifecycle$SubscriptionLambdaSubscriber.onError(FlowableDoOnLifecycle.java:85) at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209) at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:399) at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176) at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66) at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57) at java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:348) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651) at java.lang.Thread.run(Thread.java:1119) ComposedException 1 : com.polidea.rxandroidble2.exceptions.BleDisconnectedException: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 8 (GATT_INSUF_AUTHORIZATION or GATT_CONN_TIMEOUT) at com.polidea.rxandroidble2.internal.connection.RxBleGattCallback$2.onConnectionStateChange(RxBleGattCallback.java:81) at android.bluetooth.BluetoothGatt$1$4.run(BluetoothGatt.java:390) at android.bluetooth.BluetoothGatt.runOrQueueCallback(BluetoothGatt.java:1081) at android.bluetooth.BluetoothGatt.-$$Nest$mrunOrQueueCallback(Unknown Source:0) at android.bluetooth.BluetoothGatt$1.onClientConnectionState(BluetoothGatt.java:384) at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:209) at android.os.Binder.execTransactInternal(Binder.java:1523) at android.os.Binder.execTransact(Binder.java:1457) Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method com.facebook.react.bridge.PromiseImpl.reject, parameter code at com.facebook.react.bridge.PromiseImpl.reject(Unknown Source:2) at com.bleplx.utils.SafePromise.reject(SafePromise.java:25) at com.bleplx.BlePlxModule$41.onError(BlePlxModule.java:791) at com.bleplx.adapter.utils.SafeExecutor.error(SafeExecutor.java:30) at com.bleplx.adapter.BleModule.lambda$safeMonitorCharacteristicForDevice$45(BleModule.java:1485) at com.bleplx.adapter.BleModule.$r8$lambda$JVuqIGnSfaxzZFLoyHm91xQUhdI(Unknown Source:0) at com.bleplx.adapter.BleModule$$ExternalSyntheticLambda3.accept(D8$$SyntheticClass:0) at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:79) at io.reactivex.internal.operators.flowable.FlowableDoOnEach$DoOnEachSubscriber.onError(FlowableDoOnEach.java:111) at io.reactivex.internal.operators.flowable.FlowableDoOnLifecycle$SubscriptionLambdaSubscriber.onError(FlowableDoOnLifecycle.java:85) at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209) at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:399) at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176) at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66) at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57) at java.util.concurrent.FutureTask.run(FutureTask.java:317) 2025-09-04 15:53:51.086 17235-733 AndroidRuntime com.example.dev E at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:348) (Ask Gemini) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651) at java.lang.Thread.run(Thread.java:1119) ComposedException 2 : java.lang.NullPointerException: Parameter specified as non-null is null: method com.facebook.react.bridge.PromiseImpl.reject, parameter code at com.facebook.react.bridge.PromiseImpl.reject(Unknown Source:2) at com.bleplx.utils.SafePromise.reject(SafePromise.java:25) at com.bleplx.BlePlxModule$41.onError(BlePlxModule.java:791) at com.bleplx.adapter.utils.SafeExecutor.error(SafeExecutor.java:30) at com.bleplx.adapter.BleModule.lambda$safeMonitorCharacteristicForDevice$45(BleModule.java:1485) at com.bleplx.adapter.BleModule.$r8$lambda$JVuqIGnSfaxzZFLoyHm91xQUhdI(Unknown Source:0) at com.bleplx.adapter.BleModule$$ExternalSyntheticLambda3.accept(D8$$SyntheticClass:0) at io.reactivex.internal.subscribers.LambdaSubscriber.onError(LambdaSubscriber.java:79) at io.reactivex.internal.operators.flowable.FlowableDoOnEach$DoOnEachSubscriber.onError(FlowableDoOnEach.java:111) at io.reactivex.internal.operators.flowable.FlowableDoOnLifecycle$SubscriptionLambdaSubscriber.onError(FlowableDoOnLifecycle.java:85) at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.checkTerminated(FlowableObserveOn.java:209) at io.reactivex.internal.operators.flowable.FlowableObserveOn$ObserveOnSubscriber.runAsync(FlowableObserveOn.java:399) at io.reactivex.internal.operators.flowable.FlowableObserveOn$BaseObserveOnSubscriber.run(FlowableObserveOn.java:176) at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66) at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57) at java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:348) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651) at java.lang.Thread.run(Thread.java:1119) 2025-09-04 15:53:51.088 3181-5341 OplusAppStartupMonitor system_server D notifyUnstableAppInfo: Bundle[{unstableTime=1756981431088, reason=crash, userId=0, exceptionMsg=Parameter specified as non-null is null: method com.facebook.react.bridge.PromiseImpl.reject, parameter code, exceptionClass=java.lang.NullPointerException, app_channel_type=unstable, packageName=com.example.dev, unstable_restrict_switch=true}] 2025-09-04 15:53:51.090 3181-5676 OplusEapMa...sendEvent: system_server W com.example.dev happenedcrash 2025-09-04 15:53:51.101 3181-5676 ActivityTaskManager system_server W Force finishing activity com.example.dev/com.nilesecure.MainActivity