HelloCW
HelloCW

Reputation: 2335

Why can't a State<T> variable fire UI update when it is assigned by a new value?

The Code A is based a Android offical sample project here.

The Code A can display correct data in UI.

If I use Code B, I find that nothing is displayed.

It seems that _uiState=_uiState.copy(...) doesn't make uiState to notice UI that the data has changed in Code B.

What is wrong with Code B?

Code A

class InterestsViewModel(
    private val interestsRepository: InterestsRepository
) : ViewModel() {

    // UI state exposed to the UI
    private var _uiState by mutableStateOf (InterestsUiState(loading = true))  //My
    val uiState: InterestsUiState = _uiState //My

    private fun refreshAll() {
        _uiState .loading = true //My

        viewModelScope.launch {
            // Trigger repository requests in parallel
            val topicsDeferred = async { interestsRepository.getTopics() }
            val peopleDeferred = async { interestsRepository.getPeople() }
            val publicationsDeferred = async { interestsRepository.getPublications() }

            // Wait for all requests to finish
            val topics = topicsDeferred.await().successOr(emptyList())
            val people = peopleDeferred.await().successOr(emptyList())
            val publications = publicationsDeferred.await().successOr(emptyList())

            _uiState.loading=false //My
            _uiState.topics=topics //My
            _uiState.people=people //My
            _uiState.publications=publications //My

        }
    }

}

fun rememberTabContent(interestsViewModel: InterestsViewModel): List<TabContent> {
    // UiState of the InterestsScreen
    val uiState = interestsViewModel.uiState //My

    ...
}

data class InterestsUiState(
    var topics: List<InterestSection> = emptyList(), //My
    var people: List<String> = emptyList(),  //My
    var publications: List<String> = emptyList(), //My
    var loading: Boolean = false, //My
)

Code B

class InterestsViewModel(
    private val interestsRepository: InterestsRepository
) : ViewModel() {

    // UI state exposed to the UI
    private var _uiState by mutableStateOf (InterestsUiState(loading = true))
    val uiState: InterestsUiState = _uiState

    private fun refreshAll() {
        _uiState .loading = true

        viewModelScope.launch {
            // Trigger repository requests in parallel
            val topicsDeferred = async { interestsRepository.getTopics() }
            val peopleDeferred = async { interestsRepository.getPeople() }
            val publicationsDeferred = async { interestsRepository.getPublications() }

            // Wait for all requests to finish
            val topics = topicsDeferred.await().successOr(emptyList())
            val people = peopleDeferred.await().successOr(emptyList())
            val publications = publicationsDeferred.await().successOr(emptyList())

            _uiState=_uiState.copy(
                loading = false,
                topics = topics,
                people = people,
                publications = publications
            )

        }
    }

}

Upvotes: 0

Views: 949

Answers (1)

Johann
Johann

Reputation: 29875

I don't see your pattern described in the official docs:

https://developer.android.com/jetpack/compose/state

It is possible it worked under an older version of Compose and doesn't work under the current version??

According to the docs, recomposition can only occur when you use mutableStateOf in conjunction with a remember and set the value property to a new value to trigger the recomposition:

val someProperty = remember { mutableStateOf(0) }
someProperty.value = 123

But this is done in a composable. If you want to trigger this within your viewmodel, you should resort to using LiveData. Here's an example:

https://stackoverflow.com/a/69718724/753632

Using mutableStateOf on its own doesn't trigger recomposition. It is only used to store the state.

Upvotes: 1

Related Questions