Search code examples
androidandroid-jetpack-composeandroid-bluetooth

How to track BluetoothAdapter state changes dynamically in Jetpack Compose?


In my app, I would like to know if a user enables/disables Bluetooth using the status bar and display a relevant piece of UI. For that, I think that I need to keep track of the current BluetoothAdapter.isEnabled status in my @Composable functions. This is what I have:

class MainActivity : ComponentActivity() {

    private val bluetoothAdapter: BluetoothAdapter by lazy {
        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        bluetoothManager.adapter
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        var isBluetoothEnabled = bluetoothAdapter.isEnabled

        setContent {
            AppTheme {
                MainScreen(isBluetoothEnabled)
            }
        }
    }
}

@Composable
private fun MainScreen(
    isBluetoothEnabled: Boolean
) {
    if (isBluetoothEnabled) {
        // Display some UI
    } else {
        // Display different UI
    }
}

When the app is launched, I can get the correct status of Bluetooth for the app, but since BluetoothAdapter.isEnabled is not part of the @Composable lifecycle, I'm not able to track it afterwards and react to changes like it's a piece of reactive state. Is there any way to achieve the behavior that I want here?


Solution

  • I was able to solve this just by using mutableStateOf. It doesn't have to be initialized inside of a @Composable function to be reactive, which is what I misunderstood in the first place.

    1. Define a mutableStateOf value and a BroadcastReceiver that will track the state of BluetoothAdapter. When Bluetooth state changes, update the value.
        private var isBluetoothEnabled = mutableStateOf(false)
    
        private val mReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
                    when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
                        BluetoothAdapter.STATE_OFF -> {
                            isBluetoothEnabled.value = false
                            Log.i("Bluetooth", "State OFF")
                        }
                        BluetoothAdapter.STATE_ON -> {
                            isBluetoothEnabled.value = true
                            Log.i("Bluetooth", "State ON")
                        }
                    }
    
                }
            }
        }
    
    1. Inside of a @Composable function, use this value as usual:
    @Composable
    private fun MainScreen(
        isBluetoothEnabled: MutableState<Boolean>
    ) {
        if (isBluetoothEnabled) {
            // Display some UI
        } else {
            // Display different UI
        }
    }
    

    And that's it!