Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Latest commit

 

History

History
130 lines (102 loc) · 4.61 KB

recipes.md

File metadata and controls

130 lines (102 loc) · 4.61 KB

Recipes

Basic Usage Example

The following code snippet makes a connection to a BluetoothDevice, reads a characteristic, then disconnects:

val serviceUuid = "F000AA80-0451-4000-B000-000000000000".toUuid()
val characteristicUuid = "F000AA83-0451-4000-B000-000000000000".toUuid()

fun connect(context: Context, device: BluetoothDevice) = launch {
    val gatt = when (val result = device.connectGatt(context)) {
        is Success -> result.gatt
        is Failure -> throw result.cause
    }

    try {
        val characteristic = gatt.getService(serviceUuid).getCharacteristic(characteristicUuid)

        val result = gatt.readCharacteristic(characteristic)
        if (result.status == BluetoothGatt.GATT_SUCCESS) {
            println("result.value = ${result.value}")
        } else {
            // read characteristic failed
        }
    } finally {
        gatt.disconnect(timeout = 30_000L)
    }
}

private suspend fun Gatt.disconnect(timeout: Long) {
    withContext(NonCancellable) {
        withTimeoutOrNull(timeout) {
            disconnect()
        }
    }
}

Coroutine Scopes

Android Activity

In the following example, the connection process is tied to the Activity lifecycle. If the Activity is destroyed (e.g. due to device rotation or navigating away from the Activity) then the connection attempt will be canceled. If it is desirable that a connection attempt proceed beyond the Activity lifecycle, then the launch can be executed using GlobalScope, in which case the Job that launch returns can be used to manually cancel the connection process (when desired).

class ExampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val bluetoothDevice: BluetoothDevice = TODO("Retrieve a `BluetoothDevice` from a scan.")

        findViewById<Button>(R.id.connect_button).setOnClickListener {
            lifecycleScope.launch {
                val result = bluetoothDevice.connectGatt(this@ExampleActivity)

                // ...
            }
        }
    }
}

Android ViewModel

Alternative to the Android Activity scope described above, if (for example) an app has an Activity specifically designed to handle the connection process, then Android Architecture Component's ViewModel's scope can be used, allowing connection attempts to be tied to the ViewModel's lifecycle:

class ExampleViewModel(application: Application) : AndroidViewModel(application) {

    fun connect(bluetoothDevice: BluetoothDevice) {
        viewModelScope.launch {
            val result = bluetoothDevice.connectGatt(getApplication())

            // ...
        }
    }
}

Gatt Operation Cancellation

Similar to the connection process, after a connection has been established, if a Coroutine is cancelled then any Gatt operation executing within the Coroutine will be cancelled.

However, unlike the connectGatt cancellation handling, an established Gatt connection will not automatically disconnect when the Coroutine executing a Gatt operation is cancelled. Be sure and disconnect your Gatt connection when no longer needed:

launch {
    val gatt: Gatt = TODO("Acquire Gatt via `BluetoothDevice.connectGatt` extension function.")
    // todo: Assign desired characteristic to `characteristic` variable.
    val value = try {
        gatt.readCharacteristic(characteristic).value
    } finally {
        gatt.disconnect(timeout = 30_000L)
    }
}

private suspend fun Gatt.disconnect(timeout: Long) {
    withContext(NonCancellable) {
        withTimeoutOrNull(timeout) {
            disconnect()
        }
    }
}

It may be desirable to manage Bluetooth Low Energy connections manually. In which case, your own CoroutineScope or GlobalScope can be used for the connection process. The returned Gatt object (after successful connection) can be stored and later used to disconnect.