mutableStateOf does not update in MainActivity

I have a Bluetooth LE scan running which filters for specific Devices. This works fine and I see that the devices get actually added to my list. But it doesn’t trigger anything on my MainActivity. Here is a snipped from the relevant code (it does work similar to a normal scanCallback from BTLE):

@ExperimentalUnsignedTypes
@ExperimentalCoroutinesApi
class BluetoothController(context: Context): ViewModel() {
    private val bluetoothManager: BluetoothManager =
        context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    private val bluetoothSdk = BluetoothSdk()
    private var lifecycleCoroutineScope: LifecycleCoroutineScope? = null
    private var recentScanJob: Job? = null
    private var binding = BluetoothBinding(context)
    val lockDeviceList: MutableState<MutableList<PSLock>> = mutableStateOf(mutableListOf())

    private val bluetoothAdapter: BluetoothAdapter = if (bluetoothManager.adapter == null) {
        throw Exception("Not supported")
    } else {
        bluetoothManager.adapter as BluetoothAdapter
    }

    val activateBluetooth: Boolean? = try {
        bluetoothAdapter.enable()
    } catch (e: Exception) {
        throw Exception(e.message.toString())
    }
    private val psScanListener = object : PSLockScanListener {
        override fun onDeviceScanned(psLock: PSLock) {
            if (fakeWhitelist.contains(psLock.name)) {
                val search = lockDeviceList.value.find { device -> device.name == psLock.name }
                if (search == null) {
                    Log.d(TAG, "lockCallback: adding $psLock")
                    val list = lockDeviceList.value
                    list.add(psLock)
                    lockDeviceList.value = list
                    Log.d("Bluetooth", "New lockDeviceList: ${lockDeviceList.value}")
                    cancelAllScanning()
                }
            }
        }

        override fun onScanError(exception: Exception) {
            TODO("Not yet implemented")
        }
    }
}

I know this is not the full code but trust me, the scanning works, here is the log:

2021-04-15 08:46:24.874 2936-2936/com.veloce.bluetoothadministrationtool D/Bluetooth: New lockDeviceList: [PSLock(name=PSLOCK, macAddress=00:49:69:2F:51, rssi=-76, batteryLevel=1, doorState=-115, lockState=2, openingTimeInSeconds=218, firmwareVersion=111), PSLock(name=Chr_1_3, macAddress=C7:82:AB:F7:AE, rssi=-62, batteryLevel=78, doorState=2, lockState=0, openingTimeInSeconds=4, firmwareVersion=10), PSLock(name=Ruv14_1_1, macAddress=E1:C1:D3:70:43, rssi=-61, batteryLevel=79, doorState=2, lockState=0, openingTimeInSeconds=3, firmwareVersion=10), PSLock(name=Had128_2, macAddress=E6:AE:52:2B:73, rssi=-71, batteryLevel=79, doorState=2, lockState=0, openingTimeInSeconds=4, firmwareVersion=10), PSLock(name=Chr_1_2, macAddress=FD:33:AB:B5:68, rssi=-61, batteryLevel=74, doorState=2, lockState=0, openingTimeInSeconds=4, firmwareVersion=10), PSLock(name=Thu9_1_1, macAddress=ED:F3:F4:DA:E0, rssi=-71, batteryLevel=79, doorState=2, lockState=0, openingTimeInSeconds=3, firmwareVersion=10)]

So the data is definitely here, but when I call bluetooth.lockDeviceList.value on the MainActivity, it does nothing.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val isOn = bluetooth.activateBluetooth

        if (isOn == true) {
            Log.d("MainActivity", "Bluetooth is active")
            getLocationPermission()
            bluetooth.startScan()
            Log.d("MainActivity", "PSLock: ${bluetooth.lockDeviceList.value}")
            setContent {
                BluetoothAdministrationToolTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(color = MaterialTheme.colors.background) {
                        Log.d("Bluetooth", "lockDeviceList: ${bluetooth.lockDeviceList.value}")
                        Column() {
                            Text("Hello")
                            Button(onClick = { bluetooth.startScan() }) {
                                Text("Start")
                            }
                            LazyColumn {
                                itemsIndexed(items = bluetooth.lockDeviceList.value) { _, device ->
                                    Log.d("Bluetooth", "YXC: ${bluetooth.availableLocks.value}")
                                    Column(modifier = Modifier.fillMaxWidth()) {
                                        Card() {
                                            Text(device.name)
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } else {
            setContent {
                Greeting(name = "bluetooth not turned on")
            }
        }
    }

I tried to call the lockDeviceList on different scopes but it doesn’t work anywhere. Does anyone know maybe whats wrong here?

Answer

With

val list = lockDeviceList.value

you’re getting the previous list instance and modifying it, so when setting it back to the state, the state considers no change has been made because the instance is still the same.

If you create a new list instance the state can refresh, for example:

val list = mutableListOf().apply { addAll(lockDeviceList.value) }