Skip to content

Commit c6a83d6

Browse files
committed
Make extension accessors have same safety as other functions
1 parent f06bd14 commit c6a83d6

File tree

2 files changed

+182
-25
lines changed

2 files changed

+182
-25
lines changed

bluetooth/src/commonMain/kotlin/Bluetooth.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,15 @@ fun Flow<RemoteCharacteristic?>.value(): Flow<ByteArray> = distinctUntilChanged(
628628
characteristic?.value() ?: emptyFlow()
629629
}
630630

631+
/**
632+
* Gets a ([Flow] of) the [ByteArray] value from a [Flow] of an [RemoteCharacteristic] or an empty [ByteArray] if data is unavailable
633+
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
634+
* @return the [Flow] of the [ByteArray] value of the [RemoteCharacteristic] in the given [Flow], or an empty [ByteArray] if data is unavailable
635+
*/
636+
fun Flow<RemoteCharacteristic?>.valueOrEmpty(): Flow<ByteArray> = distinctUntilChanged().flatMapLatest { characteristic ->
637+
characteristic?.value() ?: flowOf(byteArrayOf())
638+
}
639+
631640
/**
632641
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic]
633642
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
@@ -641,6 +650,19 @@ fun <T> Flow<RemoteCharacteristic?>.value(deserializationStrategy: Deserializati
641650
characteristic?.value(deserializationStrategy, bluetoothFormat) ?: emptyFlow()
642651
}
643652

653+
/**
654+
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic] or `null` if data is unavailable
655+
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
656+
* @param T the type of the data to receive
657+
* @param deserializationStrategy the [DeserializationStrategy] to use to deserialize the [ByteArray] to [T]
658+
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
659+
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] in the given [Flow] or `null` if data is unavailable
660+
*/
661+
fun <T> Flow<RemoteCharacteristic?>.valueOrNull(deserializationStrategy: DeserializationStrategy<T>, bluetoothFormat: BluetoothFormat = BluetoothFormat): Flow<T?> =
662+
distinctUntilChanged().flatMapLatest { characteristic ->
663+
characteristic?.value(deserializationStrategy, bluetoothFormat) ?: flowOf(null)
664+
}
665+
644666
/**
645667
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic]
646668
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
@@ -651,6 +673,16 @@ fun <T> Flow<RemoteCharacteristic?>.value(deserializationStrategy: Deserializati
651673
inline fun <reified T> Flow<RemoteCharacteristic?>.value(bluetoothFormat: BluetoothFormat = BluetoothFormat): Flow<T> =
652674
value(bluetoothFormat.serializersModule.serializer(), bluetoothFormat)
653675

676+
/**
677+
* Gets a ([Flow] of) [T] value from a [Flow] of an [RemoteCharacteristic]
678+
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
679+
* @param T the type of the data to receive
680+
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
681+
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] in the given [Flow]
682+
*/
683+
inline fun <reified T> Flow<RemoteCharacteristic?>.valueOrNull(bluetoothFormat: BluetoothFormat = BluetoothFormat): Flow<T?> =
684+
valueOrNull(bluetoothFormat.serializersModule.serializer(), bluetoothFormat)
685+
654686
/**
655687
* Gets a ([Flow] of) the [ByteArray] value from a [RemoteCharacteristic]
656688
* This method will automatically subscribe/unsubscribe to the [RemoteCharacteristic] when the [Flow] is collected
Lines changed: 150 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,194 @@
11
package com.splendo.kaluga.bluetooth.extensions
22

3-
import com.splendo.kaluga.bluetooth.Characteristic
3+
import com.splendo.kaluga.bluetooth.RemoteCharacteristic
44
import com.splendo.kaluga.bluetooth.RemoteDescriptor
5+
import com.splendo.kaluga.bluetooth.RemoteService
56
import com.splendo.kaluga.bluetooth.UUIDException
67
import com.splendo.kaluga.bluetooth.characteristics
78
import com.splendo.kaluga.bluetooth.descriptors
89
import com.splendo.kaluga.bluetooth.device.ConnectableDevice
10+
import com.splendo.kaluga.bluetooth.discoveredServices
11+
import com.splendo.kaluga.bluetooth.serialization.BluetoothFormat
912
import com.splendo.kaluga.bluetooth.services
1013
import com.splendo.kaluga.bluetooth.value
14+
import com.splendo.kaluga.bluetooth.valueOrNull
1115
import kotlinx.coroutines.flow.Flow
12-
import kotlinx.coroutines.flow.filterNotNull
1316
import kotlinx.coroutines.flow.first
17+
import kotlinx.serialization.DeserializationStrategy
1418

1519
/**
16-
* Provides access to device data flow by service, characteristic and descriptor string uuids.
20+
* Provides access to a [RemoteService] [Flow] by service string uuids.
21+
* Only emits after services have been discovered.
22+
* @param serviceUUID string service uuid representation
23+
* @throws UUIDException.InvalidFormat if [serviceUUID] is not valid
24+
* @return the [Flow] of the [com.splendo.kaluga.bluetooth.RemoteService] associated with [serviceUUID]. Flow throws [NoSuchElementException] if the service cannot be found after discovery.
25+
*/
26+
fun Flow<ConnectableDevice?>.serviceFlow(serviceUUID: String) = discoveredServices()[serviceUUID]
27+
28+
/**
29+
* Provides access to a [RemoteService] [Flow] by service string uuids.
30+
* Emits `null` if the service cannot be found.
31+
* @param serviceUUID string service uuid representation
32+
* @throws UUIDException.InvalidFormat if [serviceUUID] is not valid
33+
* @return the [Flow] of the [RemoteService] associated with [serviceUUID] or `null` if the service is not available.
34+
*/
35+
fun Flow<ConnectableDevice?>.serviceOrNullFlow(serviceUUID: String) = services().getOrNull(serviceUUID)
36+
37+
/**
38+
* Provides access to [RemoteService] by service string uuids.
39+
* The method will suspend until services have been discovered.
40+
* @param serviceUUID string service uuid representation
41+
* @throws UUIDException.InvalidFormat if [serviceUUID] is not valid
42+
* @throws NoSuchElementException if the service cannot be found after discovery.
43+
* @return the [RemoteService] associated with [serviceUUID]
44+
*/
45+
suspend fun Flow<ConnectableDevice?>.service(serviceUUID: String) = serviceFlow(serviceUUID)
46+
.first()
47+
48+
/**
49+
* Provides access to [RemoteService] by service and characteristic string uuids or `null` if not available.
1750
* @param serviceUUID string service uuid representation
18-
* @param characteristicUUID string characteristic uuid representation
1951
* @throws UUIDException.InvalidFormat
52+
* @return the [RemoteService] associated with [serviceUUID] or `null` if not available
2053
*/
21-
fun Flow<ConnectableDevice?>.dataFlow(serviceUUID: String, characteristicUUID: String) = characteristicsFlow(serviceUUID, characteristicUUID).value()
54+
suspend fun Flow<ConnectableDevice?>.serviceOrNull(serviceUUID: String) = serviceOrNullFlow(serviceUUID)
55+
.first()
2256

2357
/**
24-
* Provides access to characteristic's flow by service and characteristic string uuids.
58+
* Provides access to [RemoteCharacteristic]'s flow by service and characteristic string uuids.
59+
* Only emits after services have been discovered.
2560
* @param serviceUUID string service uuid representation
2661
* @param characteristicUUID string characteristic uuid representation
27-
* @throws UUIDException.InvalidFormat
62+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
63+
* @return the [Flow] of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Flow throws [NoSuchElementException] if the characteristic cannot be found after discovery.
2864
*/
29-
fun Flow<ConnectableDevice?>.characteristicsFlow(serviceUUID: String, characteristicUUID: String) = services().getOrNull(serviceUUID)
65+
fun Flow<ConnectableDevice?>.characteristicFlow(serviceUUID: String, characteristicUUID: String) = serviceFlow(serviceUUID)
3066
.characteristics()[characteristicUUID]
31-
.filterNotNull()
3267

3368
/**
34-
* Provides access to [Characteristic] by service and characteristic string uuids.
69+
* Provides access to [RemoteCharacteristic]'s flow by service and characteristic string uuids.
70+
* Emits `null` if the characteristic cannot be found.
71+
* @param serviceUUID string service uuid representation
72+
* @param characteristicUUID string characteristic uuid representation
73+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
74+
* @return the [Flow] of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID] or `null` if the characteristic is not available.
75+
*/
76+
fun Flow<ConnectableDevice?>.characteristicOrNullFlow(serviceUUID: String, characteristicUUID: String) = serviceOrNullFlow(serviceUUID)
77+
.characteristics().getOrNull(characteristicUUID)
78+
79+
/**
80+
* Provides access to [RemoteCharacteristic] by service and characteristic string uuids.
3581
* The method will suspend if characteristic is not available.
3682
* @param serviceUUID string service uuid representation
3783
* @param characteristicUUID string characteristic uuid representation
38-
* @throws UUIDException.InvalidFormat
84+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
85+
* @throws NoSuchElementException if the characteristic cannot be found after discovery.
86+
* @return the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]
3987
*/
40-
suspend fun Flow<ConnectableDevice?>.characteristic(serviceUUID: String, characteristicUUID: String) = services().getOrNull(serviceUUID)
41-
.characteristics()[characteristicUUID]
42-
.filterNotNull()
88+
suspend fun Flow<ConnectableDevice?>.characteristic(serviceUUID: String, characteristicUUID: String) = characteristicFlow(serviceUUID, characteristicUUID)
4389
.first()
4490

4591
/**
46-
* Provides access to descriptors's flow by service, characteristic and descriptor string uuids.
92+
* Provides access to [RemoteCharacteristic] by service and characteristic string uuids or `null` if not available.
93+
* @param serviceUUID string service uuid representation
94+
* @param characteristicUUID string characteristic uuid representation
95+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
96+
* @return the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID] or `null` if not available
97+
*/
98+
suspend fun Flow<ConnectableDevice?>.characteristicOrNull(serviceUUID: String, characteristicUUID: String) = characteristicOrNullFlow(serviceUUID, characteristicUUID)
99+
.first()
100+
101+
/**
102+
* Provides access to [RemoteDescriptor]'s flow by service, characteristic, and descriptor string uuids.
103+
* Only emits after services have been discovered.
47104
* @param serviceUUID string service uuid representation
48105
* @param characteristicUUID string characteristic uuid representation
49106
* @param descriptorUUID string descriptor uuid representation
50-
* @throws UUIDException.InvalidFormat
107+
* @throws UUIDException.InvalidFormat if [serviceUUID], [characteristicUUID], or [descriptorUUID] is not valid
108+
* @return the [Flow] of the [RemoteDescriptor] associated with [serviceUUID], [characteristicUUID], and [descriptorUUID]. Flow throws [NoSuchElementException] if the descriptor cannot be found after discovery.
51109
*/
52-
fun Flow<ConnectableDevice?>.descriptorsFlow(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) = services().getOrNull(serviceUUID)
53-
.characteristics()[characteristicUUID]
110+
fun Flow<ConnectableDevice?>.descriptorFlow(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) = characteristicFlow(serviceUUID, characteristicUUID)
54111
.descriptors()[descriptorUUID]
55-
.filterNotNull()
112+
113+
/**
114+
* Provides access to [RemoteDescriptor]'s flow by service, characteristic, and descriptor string uuids.
115+
* Emits `null` if the descriptor cannot be found.
116+
* @param serviceUUID string service uuid representation
117+
* @param characteristicUUID string characteristic uuid representation
118+
* @param descriptorUUID string descriptor uuid representation
119+
* @throws UUIDException.InvalidFormat if [serviceUUID], [characteristicUUID], or [descriptorUUID] is not valid
120+
* @return the [Flow] of the [RemoteDescriptor] associated with [serviceUUID], [characteristicUUID], and [descriptorUUID] or `null` if the descriptor is not available.
121+
*/
122+
fun Flow<ConnectableDevice?>.descriptorOrNullFlow(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) =
123+
characteristicOrNullFlow(serviceUUID, characteristicUUID)
124+
.descriptors().getOrNull(descriptorUUID)
56125

57126
/**
58127
* Provides access to [RemoteDescriptor] by service, characteristic and descriptor string uuids.
59128
* The method will suspend if descriptor is not available.
60129
* @param serviceUUID string service uuid representation
61130
* @param characteristicUUID string characteristic uuid representation
62131
* @param descriptorUUID string descriptor uuid representation
63-
* @throws UUIDException.InvalidFormat
132+
* @throws UUIDException.InvalidFormat if [serviceUUID], [characteristicUUID], or [descriptorUUID] is not valid
133+
* @throws NoSuchElementException if the descriptor cannot be found after discovery.
134+
* @return the [RemoteDescriptor] associated with [serviceUUID], [characteristicUUID], and [descriptorUUID]
64135
*/
65-
suspend fun Flow<ConnectableDevice?>.descriptor(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) = services().getOrNull(serviceUUID)
66-
.characteristics()[characteristicUUID]
67-
.descriptors()[descriptorUUID]
68-
.filterNotNull()
69-
.first()
136+
suspend fun Flow<ConnectableDevice?>.descriptor(serviceUUID: String, characteristicUUID: String, descriptorUUID: String) =
137+
descriptorFlow(serviceUUID, characteristicUUID, descriptorUUID)
138+
.first()
139+
140+
/**
141+
* Provides access to device data flow by service and characteristic string uuids.
142+
* Only emits after services have been discovered.
143+
* @param serviceUUID string service uuid representation
144+
* @param characteristicUUID string characteristic uuid representation
145+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
146+
* @return the [Flow] of the [ByteArray] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Flow throws [NoSuchElementException] if the characteristic cannot be found after discovery.
147+
*/
148+
fun Flow<ConnectableDevice?>.dataFlow(serviceUUID: String, characteristicUUID: String) = characteristicFlow(serviceUUID, characteristicUUID).value()
149+
150+
/**
151+
* Provides access to device data flow [T] by service and characteristic string uuids.
152+
* Only emits after services have been discovered.
153+
* @param T the type of the data to receive
154+
* @param serviceUUID string service uuid representation
155+
* @param characteristicUUID string characteristic uuid representation
156+
* @param deserializationStrategy the [DeserializationStrategy] to use to deserialize the [ByteArray] to [T]
157+
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
158+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
159+
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Flow throws [NoSuchElementException] if the characteristic cannot be found after discovery.
160+
*/
161+
inline fun <reified T> Flow<ConnectableDevice?>.dataFlow(
162+
serviceUUID: String,
163+
characteristicUUID: String,
164+
deserializationStrategy: DeserializationStrategy<T>,
165+
bluetoothFormat: BluetoothFormat = BluetoothFormat,
166+
) = characteristicFlow(serviceUUID, characteristicUUID).value(deserializationStrategy, bluetoothFormat)
167+
168+
/**
169+
* Provides access to device data flow by service and characteristic string uuids.
170+
* Emits and empty [ByteArray] if the service cannot be found.
171+
* @param serviceUUID string service uuid representation
172+
* @param characteristicUUID string characteristic uuid representation
173+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
174+
* @return the [Flow] of the [ByteArray] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Emits an empty [ByteArray] if the characteristic is not available.
175+
*/
176+
fun Flow<ConnectableDevice?>.dataOrEmptyFlow(serviceUUID: String, characteristicUUID: String) = characteristicOrNullFlow(serviceUUID, characteristicUUID).value()
177+
178+
/**
179+
* Provides access to device data flow [T] by service and characteristic string uuids.
180+
* Emits `null` if the descriptor cannot be found.
181+
* @param T the type of the data to receive
182+
* @param serviceUUID string service uuid representation
183+
* @param characteristicUUID string characteristic uuid representation
184+
* @param deserializationStrategy the [DeserializationStrategy] to use to deserialize the [ByteArray] to [T]
185+
* @param bluetoothFormat the [BluetoothFormat] to use to deserialize the [ByteArray] to [T]
186+
* @throws UUIDException.InvalidFormat if [serviceUUID] or [characteristicUUID] is not valid
187+
* @return the [Flow] of the [T] value of the [RemoteCharacteristic] associated with [serviceUUID] and [characteristicUUID]. Emits `null` if the descriptor cannot be found.
188+
*/
189+
inline fun <reified T> Flow<ConnectableDevice?>.dataOrNullFlow(
190+
serviceUUID: String,
191+
characteristicUUID: String,
192+
deserializationStrategy: DeserializationStrategy<T>,
193+
bluetoothFormat: BluetoothFormat = BluetoothFormat,
194+
) = characteristicOrNullFlow(serviceUUID, characteristicUUID).valueOrNull(deserializationStrategy, bluetoothFormat)

0 commit comments

Comments
 (0)