universal_ble
universal_ble copied to clipboard
`UniversalBle.onValueChanged` returns duplicate data on Android
First of all I want to thank you for your amazing effort, The plugin API is simple and straight forward
Issue: I have BLE device with two Characteristic (write: writeWithoutResponse) and (read: notify). If I connect to my BLE device and disconnect and reconnect again , the readings from the devices will be duplicated. The duplication is equal to the number of reconnection , like if you reconnect 3 times the data will be duplicated 3 times.
This issue happens only on Android
Steps to reproduce the issue:
1- Scan
2- Stop scan
3- Select BLE device and connect
4- Discover services
5- Set notifying to read characteristic
6- Write command
7- onValueChanged
called and get the result
8- Disconnect
9- Reconnect with same steps
10- onValueChanged
called and the result is repeated twice
Flutter doctor
[✓] Flutter (Channel stable, 3.16.9, on macOS 14.3.1 23D60 darwin-arm64, locale en-EG) • Flutter version 3.16.9 on channel stable at /Users/hosamhasan/fvm/versions/3.16.9 • Upstream repository https://github.com/flutter/flutter.git • Framework revision 41456452f2 (5 weeks ago), 2024-01-25 10:06:23 -0800 • Engine revision f40e976bed • Dart version 3.2.6 • DevTools version 2.28.5
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/hosamhasan/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/hosamhasan/Library/Android/sdk • Java binary at: /Users/hosamhasan/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) • All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 15.2) • Xcode at /Applications/Xcode-15.2.0.app/Contents/Developer • Build 15C500b • CocoaPods version 1.15.2
[✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2023.1) • Android Studio at /Users/hosamhasan/Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)
[✓] IntelliJ IDEA Community Edition (version 2023.3.2) • IntelliJ at /Users/hosamhasan/Applications/IntelliJ IDEA Community Edition.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart
[✓] VS Code (version 1.86.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.83.20240201
[✓] Connected device (4 available) • Lenovo TB 8505FS (mobile) • HA16YBY9 • android-arm64 • Android 10 (API 29) • Hosam’s iPhone (mobile) • 00008101-000A6D303AF8001E • ios • iOS 17.3.1 21D61 • macOS (desktop) • macos • darwin-arm64 • macOS 14.3.1 23D60 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 122.0.6261.94
[✓] Network resources • All expected network resources are available.
• No issues found!
Code
import 'dart:async';
import 'dart:developer';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
class Example extends StatefulWidget {
const Example({super.key});
@override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
final List<BleScanResult> foundDevices = [];
BleScanResult? connectedDevice;
final result = <Uint8List>[];
String textResult = '';
@override
void initState() {
super.initState();
UniversalBle.onScanResult = (scanResult) {
log(scanResult.name ?? scanResult.deviceId);
if (foundDevices.map((e) => e.deviceId).contains(scanResult.deviceId)) {
return;
}
if (scanResult.name == null) return;
foundDevices.add(scanResult);
setState(() {});
};
UniversalBle.onAvailabilityChange = (state) {
print(state.name);
};
UniversalBle.onValueChanged = (
String deviceId,
String characteristicId,
Uint8List value,
) {
result.add(value);
};
UniversalBle.onConnectionChanged =
(String deviceId, BleConnectionState state) {
print('$deviceId -- $state');
};
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Column(
children: [
Wrap(
children: [
TextButton(
onPressed: () async {
UniversalBle.startScan();
},
child: Text('Scan'),
),
TextButton(
onPressed: () async {
UniversalBle.stopScan();
},
child: Text('Stop'),
),
TextButton(
onPressed: () async {
if (connectedDevice != null) {
UniversalBle.disconnect(connectedDevice!.deviceId)
.then((value) {
connectedDevice = null;
foundDevices.clear();
textResult = '';
setState(() {});
});
}
},
child: Text('Disconnect'),
),
TextButton(
onPressed: setNotifying,
child: const Text('Notifying'),
),
TextButton(
onPressed: () async {
if (connectedDevice != null) {
await UniversalBle.discoverServices(
connectedDevice!.deviceId,
);
}
},
child: Text('Discover Services'),
),
TextButton(
onPressed: write,
child: Text('Write'),
),
],
),
Text(
'Result:\n$textResult',
textAlign: TextAlign.center,
),
Text('Scan Result'),
Expanded(
child: ListView(
children: [
...foundDevices.map(
(value) {
return ListTile(
onTap: () {
UniversalBle.connect(value.deviceId);
connectedDevice = value;
},
title: Text('${value.name}'),
);
},
) ??
[],
],
),
),
],
),
),
),
);
}
void setNotifying() {
final readCharacteristicUuid = '';
final serviceUuid = '';
if (connectedDevice != null) {
UniversalBle.setNotifiable(
connectedDevice!.deviceId,
serviceUuid,
readCharacteristicUuid,
BleInputProperty.notification,
);
}
}
Future<void> write() async {
final writeCharacteristicUuid = '';
final serviceUuid = '';
final command = Uint8List.fromList([]);
if (connectedDevice != null) {
UniversalBle.writeValue(
connectedDevice!.deviceId,
serviceUuid,
writeCharacteristicUuid,
command,
BleOutputProperty.withoutResponse,
);
}
}
}
Kudos for the very detailed issue report! We will have a look and report back soon.
@HosamHasanRamadan i tried replicating this, seems to be working fine for me, can you cross check with NrfConnect app, sometimes issue can be with peripheral as well, if peripheral is not clearing characteristic handlers on disconnecting
You are probably forgetting to cancel the original onValueChanged.listen
resulting in multiple listens.
@chipweinberger onValueChanged
is a callback method, so we don't really have to call listen or anything
we just have to set a method which can handle value changes
UniversalBle.onValueChanged = (String deviceId, String characteristicId, Uint8List value) {
print('onValueChanged $deviceId, $characteristicId, ${hex.encode(value)}');
}
hence even if we set it multiple times, only latest one will get the updates
Same problem, reproduced on old gen devices only (example: Galaxy S10 on Android 7, Pixel 5 on Android 14), latest 22-24 year devices works fine. My situation: BLE Heart Rate device, which activated automatically by contacts touch event, App detects device in Search by name and service and automatically connect to Characteristic update via setNotifiable(). After disconnect by timeout (BLE contacts was untouched), App reconnected to device again, and received double, triple, etc onValueChanged callback.