DEV Community

myougaTheAxo
myougaTheAxo

Posted on • Originally published at zenn.dev

BLE Bluetooth Low Energy + Compose Guide

BLE Bluetooth Low Energy + Compose Guide

Integrate Bluetooth Low Energy for IoT devices. Handle permissions, scanning, and GATT communication in Compose.

Permissions Setup

// AndroidManifest.xml
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Enter fullscreen mode Exit fullscreen mode

Request runtime permissions:

val permissionLauncher = rememberLauncherForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    // Handle permission results
}
Enter fullscreen mode Exit fullscreen mode

BLE Scanning

class BleViewModel : ViewModel() {
    private val _devices = MutableStateFlow<List<BluetoothDevice>>(emptyList())
    val devices: StateFlow<List<BluetoothDevice>> = _devices.asStateFlow()

    fun startScanning(context: Context) {
        val bluetoothManager = context.getSystemService(BluetoothManager::class.java)
        val scanner = bluetoothManager.adapter.bluetoothLeScanner

        val scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                val device = result.device
                _devices.value = (_devices.value + device).distinctBy { it.address }
            }
        }

        scanner.startScan(scanCallback)
    }
}
Enter fullscreen mode Exit fullscreen mode

GATT Connection

fun connectToDevice(context: Context, device: BluetoothDevice) {
    val gattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                gatt.discoverServices()
            }
        }

        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                val services = gatt.services
                // Handle discovered services
            }
        }
    }

    device.connectGatt(context, false, gattCallback)
}
Enter fullscreen mode Exit fullscreen mode

Compose UI

@Composable
fun BleDeviceList(viewModel: BleViewModel) {
    val devices by viewModel.devices.collectAsState()

    LazyColumn {
        items(devices) { device ->
            DeviceCard(device = device, onClick = { viewModel.connectToDevice(it) })
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Always check permissions before scanning
  • Stop scanning when not needed (battery drain)
  • Handle GATT disconnections gracefully
  • Use coroutines for background scanning

8 Android app templates on Gumroad

Top comments (0)