diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ed6ef7..32e60d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 2.1.1 * Android: Fix BluetoothDevice null-safety compile error under Kotlin 2.x +* Fix `BleCharacteristic` equality and `hashCode` to compare `properties` by value ## 2.1.0 * Add optional `queueId` parameter to all APIs diff --git a/lib/src/models/ble_service.dart b/lib/src/models/ble_service.dart index 82a59972..8297d2da 100644 --- a/lib/src/models/ble_service.dart +++ b/lib/src/models/ble_service.dart @@ -1,4 +1,4 @@ -import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:universal_ble/src/universal_ble.g.dart'; import 'package:universal_ble/universal_ble.dart'; @@ -71,14 +71,18 @@ class BleCharacteristic { bool operator ==(Object other) { if (other is! BleCharacteristic) return false; if (other.uuid != uuid) return false; - if (other.properties != properties) return false; + if (!listEquals(other.properties, properties)) return false; if (other.metaData?.deviceId != metaData?.deviceId) return false; if (other.metaData?.serviceId != metaData?.serviceId) return false; return true; } @override - int get hashCode => uuid.hashCode ^ properties.hashCode ^ metaData.hashCode; + int get hashCode => Object.hash( + uuid, + Object.hashAll(properties), + metaData, + ); } class BleDescriptor { diff --git a/test/ble_characteristic_equality_test.dart b/test/ble_characteristic_equality_test.dart new file mode 100644 index 00000000..2cd75f70 --- /dev/null +++ b/test/ble_characteristic_equality_test.dart @@ -0,0 +1,53 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:universal_ble/universal_ble.dart'; + +void main() { + group('BleCharacteristic equality', () { + test('equal when uuid and properties have equal content', () { + // Two separate list instances with the same content. + final a = BleCharacteristic('2a00', [ + CharacteristicProperty.read, + CharacteristicProperty.notify, + ], []); + final b = BleCharacteristic('2a00', [ + CharacteristicProperty.read, + CharacteristicProperty.notify, + ], []); + + expect(a == b, isTrue); + // Equal objects MUST have equal hashCodes (equality contract). + expect(a.hashCode, b.hashCode); + }); + + test('not equal when properties differ', () { + final a = BleCharacteristic('2a00', [CharacteristicProperty.read], []); + final b = BleCharacteristic('2a00', [CharacteristicProperty.write], []); + + expect(a == b, isFalse); + }); + + test('not equal when uuid differs', () { + final a = BleCharacteristic('2a00', [CharacteristicProperty.read], []); + final b = BleCharacteristic('2a01', [CharacteristicProperty.read], []); + + expect(a == b, isFalse); + }); + + test('usable as Set/Map key with equal-content instances', () { + final a = BleCharacteristic('2a00', [ + CharacteristicProperty.read, + CharacteristicProperty.notify, + ], []); + final b = BleCharacteristic('2a00', [ + CharacteristicProperty.read, + CharacteristicProperty.notify, + ], []); + + final set = {a}; + expect(set.contains(b), isTrue); + + final map = {a: true}; + expect(map.containsKey(b), isTrue); + }); + }); +}