shahar keysar
shahar keysar

Reputation: 887

_uiState.value = uiState.value.copy() don't cause recompose

There is some unresolved situation that happens with my code, my target is to change the property of an item in the "devices" list (update a boolean), The change should cause recompose of the view, but that doesn't happen, In addition, I can see that the item indeed changes with the debugger, but it also causes to add an additional item(an old copy without the included change) to show up in my list.

Is there any idea how I was wrong with the new value assignment?

The ViewModel

private val _uiState = mutableStateOf(BTPairingState())
val uiState: State<BTPairingState> get() = _uiState 

This is how i edit the item in the list

    if (handlerDeviceResponse.status != null) {            
        viewModelScope.launch {
            uiState.value.devices.find { it.macAddress == handlerDeviceResponse.device.macAddress }?.isConnected = handlerDeviceResponse.status
            _uiState.value = uiState.value.copy()
        }
    }

BTPairingState:

data class BTPairingState (
    val devices: MutableList<BtDeviceItemUiModel> = mutableListOf(),
    val deviceType: DeviceType = DeviceType.RFID,
)

The Data class

data class BtDeviceItemUiModel(
    val name: String,
    val macAddress : String,
    var isConnected: Boolean = false
)

The Screen:

@Destination
@Composable
fun BTPairScreen(
    viewModel: BTPairViewModel = hiltViewModel(),
) {

    val state = viewModel.uiState
    BTPairDevices(state.value.devices) { viewModel.deviceItemClicked(it) }


Upvotes: 2

Views: 1764

Answers (3)

Antares
Antares

Reputation: 618

You can set a policy to control how the result of mutableStateOf report and merge changes to the state object.

If you writes:

private val _uiState = mutableStateOf(BTPairingState(), neverEqualPolicy())

every write to _uiState.value will trigger recomposition, but you should of course consider pros and cons of such behavior. You can read more here:

https://developer.android.com/reference/kotlin/androidx/compose/runtime/SnapshotMutationPolicy

Upvotes: 0

Hamed Goharshad
Hamed Goharshad

Reputation: 661

In your viewmodel Change state to StateFlow

private val _uiState = MutableStateFlow(BTPairingState())
val uiState: StateFlow<BTPairingState> = _uiState.asStateFlow()

In your BTPairScreen Instead of

val state = viewModel.uiState

Use:

val state by viewModel.state.collectAsState()

Upvotes: 0

CommonsWare
CommonsWare

Reputation: 1007554

Replace var isConnected by val isConnected.

Then, replace val devices: MutableList<BtDeviceItemUiModel> = mutableListOf() by val devices: List<BtDeviceItemUiModel> = emptyList().

IOW, stop using mutable values inside of your state.

Then, you can revise your code to update your MutableState with a new value, using something like:

    if (handlerDeviceResponse.status != null) {            
            val newDevices = uiState.value.devices.map { device ->
              if (device.macAddress == handlerDeviceResponse.device.macAddress) {
                device.copy(isConnected = handlerDeviceResponse.status)
              } else {
                it
              }
            }
            _uiState.value = uiState.value.copy(devices = newDevices)
    }

Upvotes: 4

Related Questions