0

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
            )

        }
    }

}
HelloCW
  • 843
  • 22
  • 125
  • 310
  • Is your code A working? Isn't this the same as Code B in [this](https://stackoverflow.com/questions/69732128/why-cant-i-use-statet-in-viewmodel-directly-with-android-studio) question which wasn't working? – Arpit Shukla Oct 27 '21 at 05:58
  • if your UI depends on attributes I think it will not change, till the object reference is not changed for the mutable state. – DeePanShu Oct 27 '21 at 06:05
  • To Arpit Shukla: Thanks. The Code A can works well, I don't know why. You can download it to test https://www.dropbox.com/s/5wuz1zrprgna3di/JetNews.zip?dl=0 – HelloCW Oct 27 '21 at 07:02

1 Answers1

1

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.

Johann
  • 27,536
  • 39
  • 165
  • 279
  • Thanks! Now the project use `ext.compose_version = '1.1.0-alpha06'`, Code A can works well, I don't know why, You can download it to test https://www.dropbox.com/s/5wuz1zrprgna3di/JetNews.zip?dl=0 – HelloCW Oct 27 '21 at 07:05
  • Thanks again ! In the article https://stackoverflow.com/a/69718724/753632 , "you need not remember when the MutableState is inside view model". So what you said "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" is right? – HelloCW Oct 27 '21 at 07:56
  • I just ran a test to see if you can use mutableStateOf in a viewmodel. You can use this instead of LiveData but you are still stuck with the same issue of no recomposition happening if you update your object's properties. Observables only detect changes to the objects they are bound to and not to their properties. You could create a new object and set the "value" property but that is usually not a great idea. The solution I gave in the link works by using a general purpose trigger observable that updates when a random number is used. You still need to set the "value" property. – Johann Oct 27 '21 at 10:08
  • I updated the code in the linked post to show it using mutableStateOf instead of using LiveData. – Johann Oct 27 '21 at 10:27