Skip to content

Commit efe6ec2

Browse files
authored
Reduce reliance on CoroutineScope (#427)
As identified in #378, `Peripheral` objects eagerly lean on the `CoroutineScope` receiver provided at `Peripheral` creation. This isn't necessary and this is a small step in removing the crutches that were being relied on.
1 parent 8fe2f08 commit efe6ec2

6 files changed

Lines changed: 22 additions & 34 deletions

File tree

core/src/androidMain/kotlin/BluetoothDevice.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.juul.kable.logs.Logging
1717
import kotlinx.coroutines.CoroutineDispatcher
1818
import kotlinx.coroutines.ExecutorCoroutineDispatcher
1919
import kotlinx.coroutines.android.asCoroutineDispatcher
20+
import kotlinx.coroutines.flow.MutableSharedFlow
2021
import kotlinx.coroutines.flow.MutableStateFlow
2122
import kotlinx.coroutines.newSingleThreadContext
2223

@@ -68,6 +69,7 @@ internal fun BluetoothDevice.connect(
6869
phy: Phy,
6970
state: MutableStateFlow<State>,
7071
mtu: MutableStateFlow<Int?>,
72+
onCharacteristicChanged: MutableSharedFlow<ObservationEvent<ByteArray>>,
7173
logging: Logging,
7274
threading: Threading,
7375
invokeOnClose: () -> Unit,
@@ -76,7 +78,7 @@ internal fun BluetoothDevice.connect(
7678
// Disconnected before the connection request has kicked off the Connecting state (via Callback).
7779
state.value = State.Connecting.Bluetooth
7880

79-
val callback = Callback(state, mtu, logging, address)
81+
val callback = Callback(state, mtu, onCharacteristicChanged, logging, address)
8082

8183
val bluetoothGatt = when {
8284
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {

core/src/androidMain/kotlin/Connection.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ package com.juul.kable
22

33
import android.bluetooth.BluetoothGatt
44
import android.bluetooth.BluetoothGatt.GATT_SUCCESS
5-
import com.juul.kable.ObservationEvent.CharacteristicChange
65
import com.juul.kable.gatt.Callback
76
import com.juul.kable.gatt.GattStatus
87
import com.juul.kable.logs.Logger
98
import com.juul.kable.logs.Logging
109
import kotlinx.coroutines.CoroutineDispatcher
11-
import kotlinx.coroutines.flow.map
1210
import kotlinx.coroutines.sync.Mutex
1311
import kotlinx.coroutines.sync.withLock
1412
import kotlinx.coroutines.withContext
@@ -33,11 +31,6 @@ internal class Connection(
3331

3432
private val logger = Logger(logging, tag = "Kable/Connection", identifier = bluetoothGatt.device.address)
3533

36-
val characteristicChanges = callback.onCharacteristicChanged
37-
.map { (bluetoothGattCharacteristic, value) ->
38-
CharacteristicChange(bluetoothGattCharacteristic.toLazyCharacteristic(), value)
39-
}
40-
4134
private val lock = Mutex()
4235
private var pending = false
4336

core/src/androidMain/kotlin/Peripheral.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import kotlinx.atomicfu.atomic
3131
import kotlinx.atomicfu.updateAndGet
3232
import kotlinx.coroutines.CoroutineScope
3333
import kotlinx.coroutines.CoroutineStart.LAZY
34-
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
3534
import kotlinx.coroutines.Deferred
3635
import kotlinx.coroutines.Job
3736
import kotlinx.coroutines.SupervisorJob
@@ -156,6 +155,7 @@ public class AndroidPeripheral internal constructor(
156155
phy,
157156
_state,
158157
_mtu,
158+
observers.characteristicChanges,
159159
logging,
160160
threading,
161161
invokeOnClose = { connectJob.value = null },
@@ -165,13 +165,7 @@ public class AndroidPeripheral internal constructor(
165165
/** Creates a connect [Job] that completes when connection is established, or failure occurs. */
166166
private fun connectAsync() = scope.async(start = LAZY) {
167167
try {
168-
val connection = establishConnection().also { _connection = it }
169-
170-
connection
171-
.characteristicChanges
172-
.onEach(observers.characteristicChanges::emit)
173-
.launchIn(scope, start = UNDISPATCHED)
174-
168+
_connection = establishConnection()
175169
suspendUntilOrThrow<State.Connecting.Services>()
176170
discoverServices()
177171
onServicesDiscovered(ServicesDiscoveredPeripheral(this@AndroidPeripheral))

core/src/androidMain/kotlin/gatt/Callback.kt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import android.bluetooth.BluetoothProfile.STATE_CONNECTING
1010
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
1111
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTING
1212
import com.juul.kable.ConnectionLostException
13+
import com.juul.kable.ObservationEvent
14+
import com.juul.kable.ObservationEvent.CharacteristicChange
1315
import com.juul.kable.State
1416
import com.juul.kable.State.Disconnected.Status.Cancelled
1517
import com.juul.kable.State.Disconnected.Status.CentralDisconnected
@@ -35,25 +37,20 @@ import com.juul.kable.gatt.Response.OnServicesDiscovered
3537
import com.juul.kable.logs.Logger
3638
import com.juul.kable.logs.Logging
3739
import com.juul.kable.logs.detail
40+
import com.juul.kable.toLazyCharacteristic
3841
import kotlinx.coroutines.channels.Channel
3942
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
40-
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
4143
import kotlinx.coroutines.channels.SendChannel
4244
import kotlinx.coroutines.channels.onFailure
43-
import kotlinx.coroutines.flow.Flow
45+
import kotlinx.coroutines.flow.MutableSharedFlow
4446
import kotlinx.coroutines.flow.MutableStateFlow
45-
import kotlinx.coroutines.flow.consumeAsFlow
4647

4748
private typealias DisconnectedAction = () -> Unit
4849

49-
internal data class OnCharacteristicChanged(
50-
val characteristic: BluetoothGattCharacteristic,
51-
val value: ByteArray,
52-
)
53-
5450
internal class Callback(
5551
private val state: MutableStateFlow<State>,
5652
private val mtu: MutableStateFlow<Int?>,
53+
private val onCharacteristicChanged: MutableSharedFlow<ObservationEvent<ByteArray>>,
5754
logging: Logging,
5855
macAddress: String,
5956
) : BluetoothGattCallback() {
@@ -65,10 +62,6 @@ internal class Callback(
6562
disconnectedAction = action
6663
}
6764

68-
private val _onCharacteristicChanged = Channel<OnCharacteristicChanged>(UNLIMITED)
69-
val onCharacteristicChanged: Flow<OnCharacteristicChanged> =
70-
_onCharacteristicChanged.consumeAsFlow()
71-
7265
val onResponse = Channel<Response>(CONFLATED)
7366
val onMtuChanged = Channel<OnMtuChanged>(CONFLATED)
7467

@@ -126,7 +119,6 @@ internal class Callback(
126119
}
127120

128121
if (newState == STATE_DISCONNECTING || newState == STATE_DISCONNECTED) {
129-
_onCharacteristicChanged.close()
130122
onResponse.close(ConnectionLostException())
131123
}
132124
}
@@ -175,13 +167,13 @@ internal class Callback(
175167
characteristic: BluetoothGattCharacteristic,
176168
) {
177169
val value = characteristic.value
178-
val event = OnCharacteristicChanged(characteristic, value)
179170
logger.debug {
180171
message = "onCharacteristicChanged"
181172
detail(characteristic)
182173
detail(value)
183174
}
184-
_onCharacteristicChanged.trySendOrLog(event)
175+
val event = CharacteristicChange(characteristic.toLazyCharacteristic(), value)
176+
onCharacteristicChanged.tryEmitOrLog(event)
185177
}
186178

187179
override fun onDescriptorRead(
@@ -261,6 +253,14 @@ internal class Callback(
261253
}
262254
}
263255
}
256+
257+
private fun <E> MutableSharedFlow<E>.tryEmitOrLog(element: E) {
258+
if (!tryEmit(element)) {
259+
logger.warn {
260+
message = "Callback was unable to deliver $element"
261+
}
262+
}
263+
}
264264
}
265265

266266
private val Int.disconnectedConnectionStatus: State.Disconnected.Status?

core/src/commonMain/kotlin/Observers.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,10 @@ internal expect fun Peripheral.observationHandler(): Observation.Handler
4040
internal class Observers<T>(
4141
private val peripheral: Peripheral,
4242
private val logging: Logging,
43-
extraBufferCapacity: Int = 0,
4443
private val exceptionHandler: ObservationExceptionHandler,
4544
) {
4645

47-
val characteristicChanges = MutableSharedFlow<ObservationEvent<T>>(extraBufferCapacity = extraBufferCapacity)
46+
val characteristicChanges = MutableSharedFlow<ObservationEvent<T>>(extraBufferCapacity = Int.MAX_VALUE)
4847
private val observations = Observations()
4948

5049
fun acquire(

core/src/jsMain/kotlin/Peripheral.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ public class JsPeripheral internal constructor(
298298
.buffer
299299
.toByteArray()
300300

301-
private val observers = Observers<DataView>(this, logging, extraBufferCapacity = 64, observationExceptionHandler)
301+
private val observers = Observers<DataView>(this, logging, observationExceptionHandler)
302302

303303
public fun observeDataView(
304304
characteristic: Characteristic,

0 commit comments

Comments
 (0)