You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
🐛 Android Crash on Second Connection After App Relaunch (monitorCharacteristicForDevice) #1332
I checked the documentation and FAQ without finding a solution
I checked to make sure that this issue has not already been filed
Expected Behavior
The application crashes on Android when connecting to a BLE device for the second time after closing and reopening the app. The crash occurs when navigating to the reading screen where monitorCharacteristicForDevice() is used.
Current Behavior
This seems related to a known issue where the app crashes instead of handling disconnection properly.
Library version
3.5.0
Device
all devices
Environment info
The application crashes on Android when connecting to a BLE device for the second time after closing and reopening the app. The crash occurs when navigating to the reading screen where monitorCharacteristicForDevice() is used.
Steps to reproduce
…
…Install and open the application on an Android device.
Scan and connect to a BLE peripheral device.
Navigate to the reading/data screen where monitorCharacteristicForDevice() is started.
Verify that data is being received correctly.
Close or kill the application from recent apps (without manually disconnecting the BLE device).
03-23 11:10:43.064 28371 28425 I ReactNativeJS: params: { devicename: 'LeftFingerSensor' },
03-23 11:10:43.064 28371 28425 I ReactNativeJS: path: undefined } ],
03-23 11:10:43.064 28371 28425 I ReactNativeJS: preloadedRouteKeys: [],
03-23 11:10:43.064 28371 28425 I ReactNativeJS: default: 'closed' }
03-23 11:10:43.096 28371 28425 I ReactNativeJS: 'TestsStack', 'activeRoute'
03-23 11:10:43.543 28534 28564 E BtGatt.ContextMap: Context not found for ID 6
03-23 11:10:43.543 28534 28564 E BtGatt.ContextMap: Context not found for ID 7
03-23 11:10:43.543 28534 28564 E BtGatt.ContextMap: Context not found for ID 8
03-23 11:10:44.591 684 822 W BluetoothEventManager: AclStateChangedHandler: activeDevice is null
03-23 11:10:44.614 684 822 W BluetoothEventManager: AclStateChangedHandler: activeDevice is null
03-23 11:10:44.621 684 822 W BluetoothEventManager: AclStateChangedHandler: activeDevice is null
I had a quick look as I don't have this issue but I couldn't see anything obvious. It's difficult without having all the code indented.
I still use 3.2.6, I think after this Promise issues were introduced and I'm not confident they are fixed. There's a lack of testing with this library.
The other thing is a few of the calls are passing the uuid but it's not what is expected under the hood, iirc it's the uuid mapping id, a number. When it gets through to android/ios it usually works still as they must have some compat code in there but again I'm not 100% sold on it always working. Instead I use only device.*, i. e device.monitorCharacteristicForService calls.
Since that's an easy change I'd try that first, then try downgrading to 3.2.*. This library is pretty much abandoned and is not in a good state.
Edit:
I had a look again in my code. When your App 'exits' is it going into the background, (the default behaviour)? If so you need to remove all the monitorCharacteristics and set them back up when returning to foreground. See AppState.addEventListener('change', ...
I can't recall why I had to do this other than good practice for battery life. Something tells me it was to do with if the App isn't a service or have background permissions Android semi terminates the Bluetooth after a small delay and it errors the subscriptions.
Hi, just wanted to flag that this review comment appears to be a duplicate of an earlier issue #1333#1329 already discussed/resolved in the PR thread.
The same concern was raised previously, responded to, and addressed in follow-up commits, but seems to have been reopened again without considering the latest changes.
please take a look and close the duplicate thread if appropriate?
Activity
rrubankumar-ctrl commentedon Mar 20, 2026
useFocusEffect(
useCallback(() => {
isMountedRef.current = true;
);
const onClickNewTest = async () => {
cancelListenersRef.current.forEach((stopFn) => {
try {
stopFn?.();
} catch (e) {
console.warn("Error stopping listener:", e);
}
});
cancelListenersRef.current = [];
};
rrubankumar-ctrl commentedon Mar 23, 2026
export const monitorDeviceSensorData = (
device: Device,
onLiveData?: (data: any) => void,
timeout?: number
): { promise: Promise<any[]>; stop: () => void } => {
const SERVICE_UUID = FINGER_SERVICE;
const UUID_FINGER_TEMP = IMU_UUIDS.FINGER_TEMP;
const UUID_ROOM_TEMP = IMU_UUIDS.ROOM_TEMP;
const UUID_IMU = IMU_UUIDS.RAW_IMU_DATA;
const sensorDataArray: any[] = [];
let latestData: any = {
fingerTemp: null,
roomTemp: null,
imu: null,
imuEvent: null as "significant_motion" | "wrist_tilt" | "wake_up" | null,
timestamp: null,
};
let isStopped = false;
const imuEventSubscriptions: { remove: () => void }[] = [];
let imuDataPollIntervalId: ReturnType | null = null;
let imuEventPollIntervalId: ReturnType | null = null;
const pushAndEmit = () => {
const snapshot = { ...latestData };
sensorDataArray.push(snapshot);
onLiveData?.(snapshot);
};
const onIMUEvent = (event: "significant_motion" | "wrist_tilt" | "wake_up") => {
if (isStopped) return;
latestData.imuEvent = event;
latestData.timestamp = new Date().toISOString();
console.log("[BLE] IMU event:", event);
pushAndEmit();
setTimeout(() => {
if (!isStopped) latestData.imuEvent = null;
}, 2000);
};
let fingerTempSubscription: { remove: () => void } | null = null;
let roomTempSubscription: { remove: () => void } | null = null;
try {
fingerTempSubscription = device.monitorCharacteristicForService(
SERVICE_UUID,
UUID_FINGER_TEMP,
(error, characteristic) => {
if (!isStopped && !error && characteristic?.value) {
latestData.fingerTemp = parseFingerTemp(characteristic.value);
latestData.timestamp = new Date().toISOString();
pushAndEmit();
}
}
);
} catch (e) {
console.warn("[BLE] finger temp monitor failed:", (e as Error)?.message ?? e);
}
try {
roomTempSubscription = device.monitorCharacteristicForService(
SERVICE_UUID,
UUID_ROOM_TEMP,
(error, characteristic) => {
if (!isStopped && !error && characteristic?.value) {
latestData.roomTemp = parseRoomTemp(characteristic.value);
latestData.timestamp = new Date().toISOString();
if (DEV) console.log("[BLE] roomTemp delivered", latestData.roomTemp);
pushAndEmit();
}
}
);
} catch (e) {
console.warn("[BLE] room temp monitor failed:", (e as Error)?.message ?? e);
}
let imuSubscription: { remove: () => void } | null = null;
// IMU: discover service. For c600 use polling only (no notify). Else try subscribe then config + event polling.
const IMU_DATA_POLL_MS = 150;
const IMU_POLL_READ_TIMEOUT_MS = 4000;
let imuDataPollingStarted = false;
let imuC600NotifyFallbackDone = false;
let imuDiscoveryStarted = false;
let imuPollReadFailLogged = false;
let imuPollEmptyLogged = false;
let imuPollTimeoutLogged = false;
const startIMUDataPolling = (svc: string, charUUID: string) => {
if (isStopped || imuDataPollIntervalId || imuDataPollingStarted) return;
imuDataPollingStarted = true;
const devId = (device?.id ?? "").slice(-8);
console.log("[BLE] IMU data: polling", charUUID, "on", svc, devId ?
[${devId}]: "");imuDataPollIntervalId = setInterval(async () => {
if (isStopped) return;
try {
const readPromise = device.readCharacteristicForService(svc, charUUID);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), IMU_POLL_READ_TIMEOUT_MS)
);
const c = await Promise.race([readPromise, timeoutPromise]);
if (isStopped) return;
if (!c?.value) {
if (!imuPollEmptyLogged) {
imuPollEmptyLogged = true;
console.log("[BLE] IMU data: read ok but value empty (char may be write-only or not streaming)");
}
return;
}
const parsed = parseIMUData(c.value);
latestData.imu = parsed;
latestData.timestamp = new Date().toISOString();
console.log("[BLE] IMU parsed", parsed);
pushAndEmit();
} catch (e) {
const msg = (e as Error)?.message ?? String(e);
if (msg === "timeout") {
if (!imuPollTimeoutLogged) {
imuPollTimeoutLogged = true;
if (imuDataPollIntervalId) {
clearInterval(imuDataPollIntervalId);
imuDataPollIntervalId = null;
}
console.log("[BLE] IMU (c604) read not available, stopped");
}
} else if (!imuPollReadFailLogged) {
imuPollReadFailLogged = true;
console.log("[BLE] IMU data: poll read failed:", msg);
}
}
}, IMU_DATA_POLL_MS);
};
const startIMUWithDiscoveredService = () => {
if (imuDiscoveryStarted) return;
imuDiscoveryStarted = true;
findIMUServiceUUID(device)
.then((imuServiceUUID) => {
if (isStopped) return;
return configureIMUBuiltInFunctions(device, imuServiceUUID).then((res) => ({ imuServiceUUID, res }));
})
.then((out) => {
if (isStopped || !out) return;
const svc = out.imuServiceUUID;
if (svc) {
console.log("[BLE] IMU starting event monitors on", svc.slice(-4));
startIMUEventMonitoring(svc);
}
})
.catch((e) => {
console.log("[BLE] IMU setup error:", (e as Error)?.message ?? e);
});
};
startIMUWithDiscoveredService();
// IMU events per BuiltInIMU.py / process_received_data: poll FUNC_SRC1, FUNC_SRC2, WAKE_UP_SRC every 500ms and parse (data&0x40 sm, data&0x1 wt, data&0x08 wu).
const startIMUEventMonitoring = (imuServiceUUID: string) => {
if (isStopped || imuEventSubscriptions.length > 0) return;
const R = IMU_UUIDS;
const DEBOUNCE_MS = 1500;
const lastFired: Record<string, number> = {};
};
const stop = () => {
isStopped = true;
imuEventSubscriptions.forEach((sub) => {
try {
sub.remove();
} catch () {}
});
imuEventSubscriptions.length = 0;
if (imuEventPollIntervalId) {
clearInterval(imuEventPollIntervalId);
imuEventPollIntervalId = null;
}
if (imuDataPollIntervalId) {
clearInterval(imuDataPollIntervalId);
imuDataPollIntervalId = null;
}
try {
fingerTempSubscription?.remove?.();
} catch () {}
try {
roomTempSubscription?.remove?.();
} catch () {}
try {
imuSubscription?.remove();
} catch () {}
console.log("🛑 Monitoring manually stopped.");
};
const promise = new Promise<any[]>((resolve) => {
setTimeout(() => {
stop(); // Auto stop after timeout
resolve(sensorDataArray);
}, timeout ?? TIMEOUT_15_MINUTES);
});
return { promise, stop };
};
rrubankumar-ctrl commentedon Mar 23, 2026
03-23 11:10:43.064 28371 28425 I ReactNativeJS: params: { devicename: 'LeftFingerSensor' },
03-23 11:10:43.064 28371 28425 I ReactNativeJS: path: undefined } ],
03-23 11:10:43.064 28371 28425 I ReactNativeJS: preloadedRouteKeys: [],
03-23 11:10:43.064 28371 28425 I ReactNativeJS: default: 'closed' }
03-23 11:10:43.096 28371 28425 I ReactNativeJS: 'TestsStack', 'activeRoute'
03-23 11:10:43.543 28534 28564 E BtGatt.ContextMap: Context not found for ID 6
03-23 11:10:43.543 28534 28564 E BtGatt.ContextMap: Context not found for ID 7
03-23 11:10:43.543 28534 28564 E BtGatt.ContextMap: Context not found for ID 8
03-23 11:10:44.591 684 822 W BluetoothEventManager: AclStateChangedHandler: activeDevice is null
03-23 11:10:44.614 684 822 W BluetoothEventManager: AclStateChangedHandler: activeDevice is null
03-23 11:10:44.621 684 822 W BluetoothEventManager: AclStateChangedHandler: activeDevice is null
rrubankumar-ctrl commentedon Mar 23, 2026
@aliberski ....Hi can you have any answer
rrubankumar-ctrl commentedon Mar 23, 2026
android 11
andrewcharnley commentedon Apr 10, 2026
I had a quick look as I don't have this issue but I couldn't see anything obvious. It's difficult without having all the code indented.
I still use 3.2.6, I think after this Promise issues were introduced and I'm not confident they are fixed. There's a lack of testing with this library.
The other thing is a few of the calls are passing the uuid but it's not what is expected under the hood, iirc it's the uuid mapping id, a number. When it gets through to android/ios it usually works still as they must have some compat code in there but again I'm not 100% sold on it always working. Instead I use only device.*, i. e device.monitorCharacteristicForService calls.
Since that's an easy change I'd try that first, then try downgrading to 3.2.*. This library is pretty much abandoned and is not in a good state.
Edit:
I had a look again in my code. When your App 'exits' is it going into the background, (the default behaviour)? If so you need to remove all the monitorCharacteristics and set them back up when returning to foreground. See
AppState.addEventListener('change', ...I can't recall why I had to do this other than good practice for battery life. Something tells me it was to do with if the App isn't a service or have background permissions Android semi terminates the Bluetooth after a small delay and it errors the subscriptions.
p-dot-max commentedon May 8, 2026
Hi, just wanted to flag that this review comment appears to be a duplicate of an earlier issue #1333 #1329 already discussed/resolved in the PR thread.
The same concern was raised previously, responded to, and addressed in follow-up commits, but seems to have been reopened again without considering the latest changes.
please take a look and close the duplicate thread if appropriate?