androidkotlinandroid-jetpack-composeandroid-jetpack

jetpack compose does not detect state change when i update an arraylist


i have an object which hold the state of my ui which looks like this:

data class MainActivityUiState(
    /*** rest **/
    val usersData : ArrayList<Users>
)

i intailize it like this in the view model

    private val _uiState = MutableStateFlow(
        MainActivityUiState(
              /*** rest ***/
            usersData = ArrayList()
        )
    )

    var uiState = _uiState.asStateFlow()

then when the changes happen i do this:

val listToAdd = _uiState.value.usersData
        listToAdd.add(one)

        _uiState.value = _uiState.value.copy(
            usersData = listToAdd
        )

i put a loging statement before and after so i make sure the size is adding up , and it is.

in the activity , i collect the state like this:

uiState = viewModel.uiState.collectAsState()

and the link is working because i have other states in the uistate which when changes , get captured by the ui

here how i catch the state changes for the array list

LazyColumn(
                        modifier = Modifier.padding(innerPadding)
                            .background(Color.White)
                            .fillMaxSize()
                    ) {
                        items(uiState.value.usersData ){ match ->
                            Text(match.name)
                        }
                    }

i also tried adding items directly to the ui state like this inside the viewmodel:

_uiState.value.usersData.add(one)

which didn't work


Solution

  • Your UI doesn't update because the value of the StateFlow never changes.

    A StateFlow only emits a new value when the new value you set is not equal to the previous value (i.e. !=). Let's take a look at what happens when you update the value:

    _uiState.value = _uiState.value.copy(
        usersData = listToAdd
    )
    

    Although you create a new MainActivityUiState object, its usersData poperty is identical: Both hold the exact same ArrayList object. From that follows that both MainActivityUiState objects are equal, so the StateFlow never updates. Please note that adding a new value to the list doesn't make it a different list: It is still the same list, just the content is different.

    This is a subtle bug which can be effectively prevented if you avoid using mutable lists in the first place. In your case that means you should replace your ArrayList with a simple List:

    data class MainActivityUiState(
        /*** rest **/
        val usersData: List<Users>,
    )
    

    Now the compiler prevents you to do the following so you don't fall into the trap that you think that changing the list will change the StateFlow's value:

    listToAdd.add(one)
    

    Instead you need to create a new list when you want to add the item. You can simply use the + operator for that. Updating the flow's value would then look like this:

    _uiState.update {
        it.copy(usersData = it.usersData + one)
    }
    

    Please note that I use the update method here instead of setting the value property. That has nothing to do with your initial problem, but it prevents another issue that could occur when a different thread changes the flow's value while you calculate the new value.