Francis Mariano
08/24/2021, 9:22 PMFrancis Mariano
08/24/2021, 9:22 PMobject BluetoothServiceImpl : BluetoothService {
private val scanner = Scanner()
private var peripheral: Peripheral? = null
private var _scope: CoroutineScope? = null
private val requireScope: CoroutineScope =
if (_scope == null) CoroutineScope(context = Dispatchers.Main) else _scope!!
private val _state = MutableStateFlow<State>(State.Disconnected())
override val state: StateFlow<State> = _state.asStateFlow()
override fun getAdversiments(): Flow<ScanDeviceItem> {
return scanner.advertisements.map {
ScanDeviceItem(name = it.name, macAddress = macAddressFrom(it))
}
}
override fun connectWith(macAddress: String) {
requireScope.launch {
peripheral = requirePeripheral(requireScope, macAddress)
peripheral?.state?.onEach { _state.emit(it) }?.launchIn(requireScope)
peripheral?.connect()
}
}
override fun disconnect() {
requireScope.launch {
withTimeoutOrNull(5000) {
peripheral?.disconnect()
}
}
}
override fun dispose() {
_scope?.cancel()
_scope = null
}
}
travis
08/25/2021, 2:18 AMCoroutineScope
w/ its context set to Main
, but I don't see anywhere that you need that context. Kable doesn't care what context any of its functions are called from, so using Default
might be a better choice (i.e. no need to specify the context).
To remove some of the nullability/simplify things, I'd just create the scope as a val
, for example:
private val scope = CoroutineScope(Job())
I personally prefer fail-fast approach, so I'd change your peripheral property to something like:
private var _peripheral: Peripheral? = null
private val peripheral: Peripheral
get() = _peripheral ?: error("Peripheral not ready")
Then you set peripheral via _peripheral = ...
and everywhere else use it via peripheral.*
. It eliminates all the null ?.
operators you have to use but you'll crash when you've made a programming mistake (and used it before it was ready).
Overall, your approach is a good start and would likely need to evolve if you scale the solution to (for example) support more than one peripheral at a time, etc.Francis Mariano
08/25/2021, 11:48 AMFrancis Mariano
08/25/2021, 11:57 AMfun dispose()
in this case?travis
08/25/2021, 5:44 PMYes, at first I will only connect to one peripheral at a time. But I can evolve if I need to connect to more than one. In this case, how do you show the user which peripheral he is working on?We created a class that essentially wraps a collection of
Peripheral
objects. The class acts like a collection but with very limited functionality. To ensure that we never forget to close connections, etc. It has a signature similar to:
class Peripherals {
val peripherals: StateFlow<Map<String, Peripheral>>
fun get(id: String): Peripheral
// Throws if Peripheral already exists under the specified `id`.
fun putOrThrow(id: String, peripheral: Peripheral)
suspend fun removeAndDispose(id: String)
}
So, when we initially connect to the Peripheral
we add it to this collection. Once added, that Peripheral
is available anywhere in our app via the id
. When we're done with it we simply call removeAndDispose
and we're assured it was completely cleaned up. We also update the exposed StateFlow
, so any screens that would show all peripherals would also be updated to reflect the removal.
Because we have control over the wrapper (that acts like a collection) then via the exposed Flow
we can subscribe to it on a screen that has to show all available peripherals, and on screens that only need a specific one, we use the get(id)
method.
There are certainly other approaches to address this, but this has worked well for us.
I thought about leaving the scope nullable because I can cancel it when I exit the application, that way I can force the end of a connection that might have gotten stuck.Not sure I follow what you're trying to do. When the application exists, all BLE connections should be destroyed by the Android OS anyways.
do you think it's a good approach to useSeems reasonable to me for the use case you described.in this case?fun dispose()
Francis Mariano
09/02/2021, 8:53 PMFrancis Mariano
09/03/2021, 1:45 PMWe created a class that essentially wraps a collection ofThis class will wrap all connected peripherals, correct? If the peripherals will be used by multiple screens, which must have their own viewmodel, with what scope do you create the peripherals?objectsPeripheral
travis
09/06/2021, 4:15 AMFrancis Mariano
03/01/2023, 7:55 PMfun get(id: String): Peripheral
you call peripheral.connect again or not? do the same for screen C??travis
03/01/2023, 8:29 PMMap<Identifier, Peripheral>
(where Identifier
is a type of your choosing, could be Uuid
).
You could add to that collection on screen A, connect to it, then on screen B you retrieve the Peripheral
via identifier. You shouldn’t need to call connect
again because the peripheral should remain connected.
Screen C, you just retrieve from the singleton again, like you did on screen B.Francis Mariano
03/02/2023, 12:34 PM