https://kotlinlang.org logo
#coroutines
Title
# coroutines
l

louiscad

12/06/2017, 11:22 AM
Hi, could anyone hint me at how I can convert an API with a God Callback to coroutines? This API is Bluetooth Low Energy GATT (General ATTribute) in Android, where you can only pass a
BluetoothGattCallback
instance with the overriden methods you'll need once, when connecting, and the connection result, the attributes discovery and read/write operations are passed to this callback. What I aim at is to use coroutines to handle failures without callback hell, in a procedural style, and to handle success this way too. The issue that I have is that this
BluetoothGattCallback
listens to too many different things. When connecting, I'm just interested in the connectionState callback. When writing data, I'm just interested in waiting for completion or error and knowing if it succeeded or failed, when reading data, just need the data, or exception... Is it possible to separate these things from this god callback? For reference, here is the key method with the two
BluetoothGatt
and
BluetoothGattCallback
key classes in its signature:
<https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#connectGatt(android.content.Context>, boolean, android.bluetooth.BluetoothGattCallback)
(select the whole link to get directed to the right method, slack doesn't like this url formatting) Please, reply in thread since this is quite specific to Android, so this channel doesn't get too cluttered
e

elizarov

12/06/2017, 12:00 PM
The I would do most of this things is with channels. Take connection state for example. I would define
Copy code
data class ConnectionState(val status: Int, state: Int)
val NOT_CONNECTED = ConnectionState(0, 0) // or whatever
val stateChannel = ConflatedChannel<ConnectionState>(NOT_CONNECTED)
Then, in the callaback’s
onConnectionStateChange
I would do
stateChannel.offer(ConnectionState(status, newState))
This way, when you perform connection operation from a coroutine you can write an imperative code like this:
Copy code
suspend fun connect() {
    performConnection()
    for (connectionState in stateChannel) {
        if (connectionState.isGood) break
    }
}
l

louiscad

12/06/2017, 4:42 PM
@elizarov My goal is to have an "on demand" API for BLE, to be used in a beacons setup app, channels don't seem made for that. Below is an hypothetical Kotlin code using the API I'd like to make when I know how. Version that connects and closes the connection:
Copy code
suspend fun BluetoothDevice.getBroadcastingIntervalMillis(): Int {
    val gatt = connect(bleDevice) // Suspends until connection succeeds, fails or times out
    val uuid = EddystoneServiceSpec.broadcastingIntervalUuid
    val broadcastingIntervalCharacteristic = gatt.readCharacteristic(uuid) // Suspends until characteristic is read, or throws on failure or disconnection
    gatt.close()
    return broadcastingIntervalCharacteristic.getIntValue(FORMAT_UINT32, 0)!! 
}
Version that leaves the connection open, and throws on failures or disconnections:
Copy code
suspend fun BluetoothGatt.getBroadcastingIntervalMillis(): Int {
    check(isConnected())
    val uuid = EddystoneServiceSpec.broadcastingIntervalUuid
    val broadcastingIntervalCharacteristic = gatt.readCharacteristic(uuid) // Suspends until characteristic is read, or throws
    return broadcastingIntervalCharacteristic.getIntValue(FORMAT_UINT32, 0)!! 
}
e

elizarov

12/06/2017, 7:03 PM
But who is going to invoke
connectGatt
? You should have this invocation somewhere. What I would do is to crate a class
GattConnection
that is produced as the result of that
connectGatt
invocation. This class keeps a callback and all the channels inside as private vals. Now, you can define all the methods you need in your bluetooth API as members of
GattConnection
class. They will have access to those private channels and work via them.
l

louiscad

12/07/2017, 8:25 AM
In my example, the
connect(...)
hypothetical method seen in the second line of my first code snippet would have called
connectGatt(...)
under the hood. Thanks for your hint, I will try to make it work
2 Views