Office Hours Transcript: 2021-11-09

john joined

hello, john!

 

how can I help you today?

hello Mark, I would like to learn about ViewModel.

I cover the Jetpack ViewModel system in Elements of Android Jetpack and Exploring Android. Is there anything specific that concerns you?

If my understanding is correct, we need to use them to preserve values when there is a configuration change. However,

 

I have experimented with mutableStateOf, and it seems that a viewmodel is not necessary

 

For e.g.:

class MyViewModel{

var piu = mutableStateOf(0)

fun addOne(){

   piu.value += 1

}

}

 

If you use this class directly in the MainActivity, like so

 

setContent {
TestViewModelComposeTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {

                    Text(text = vm.piu.value.toString(),
                        textAlign = TextAlign.Center,
                        fontSize = 50.sp
                    )

                    Button(onClick = { vm.addOne()}) {

                        "ADD ONE"
                    }
                }
            }
 

It works fine, so why use a viewmodel at all?

Three questions:

  1. Where is vm coming from? I do not see where you are instantiating it in that last code sample.

  2. What is the contents of your <activity> element in the manifest for MainActivity?

  3. What is your definition of "works fine"?

  1. override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val vm = MyViewModel()
    setContent { …

vm is instantiated in the mainactivity

 
  1. <activity
    android:name=".MainActivity"
    android:exported="true"
    android:label="@string/app_name"
    android:theme="@style/Theme.TestViewModelCompose.NoActionBar">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
 
  1. the value of the variable "piu" is preserved when the is a screen rotation

you might want to add some logging to your onCreate() function to see whether your activity is being destroyed and recreated

sorry for the confusing name "vm", originally it was a viewmodel than I just made it a simple class.

if your activity is not being destroyed and recreated, that would explain why your vm value seems to survive the configuration change… because there is no configuration change happening

Let me add the Log now.

I had expected to see android:configChanges in the manifest, or something else that might have suppressed configuration changes, at least for screen rotation

 

AFAIK, Compose does not really change the rules regarding configuration changes. Either:

  1. You need to suppress them and handle them in your code. Compose helps a lot with this, but AFAIK it does not automatically suppress configuration changes; or
  2. You need to use something that survives a configuration change, where that "something" could be a viewmodel or could be the saved instance state Bundle
 

if your logging shows that the activity is being destroyed and recreated, then I cannot explain your results, though I have not tried this particular experiment

Well, I do use mutableStateOf

that is not really tied to configuration changes, though

 

that is a state holder, one that Compose knows about for the purposes of recomposition

 

but otherwise it is not all that different from LiveData, or StateFlow, and those do not survive configuration changes without help

It does not get recreated when there is a rotation change.

OK, that explains why vm survives. You might try other configuration changes, besides screen rotation, such as toggling on/off dark mode, or changing your mix of languages in Settings

How to you toggle dark mode in the emulator?

if it is an Android 10 or higher emulator, there is a notification shade tile that you can use

 

I assume there’s a switch for it in Settings too – I tend to use the tile

Yep, it disappeared with dark mode

you will need to poke around to see what it is about your emulator or your project that is causing screen rotations to not trigger a configuration change

 

for example, locking a particular orientation via android:screenOrientation in the manifest would do that, though you do not seem to have that <activity> attribute

No, I did not change anything.

Perhaps it is something to do with the emulator, then. I usually work with hardware.

I see. It’s a big deal, now I know I need to check a couple of configuration changes when testing with the emulator.

the dark mode tile is an easy solution – I use that when testing apps that lock the orientation to portrait, for example

*It’s not a big deal

 

Are you familiar with this way of passing the view model?

https://developer.android.com/jetpack/compose/libraries#viewmodel

 

It’s not clear to me what the advantage is?

if you are using the Jetpack ViewModel system, you need to let it manage creating the viewmodel instances

 

viewModel() is the Compose solution for this – presumably it takes into account the fact that composables usually are top-level functions, so it uses LocalContext or something to get to a LifecycleOwner

What do you mean by "you need"? Right now I’m doing it the old way and it works fine.

I think we covered this in a previous chat – if you do not let the Jetpack ViewModel system manage creation of your viewmodel instances, then your manually-created instances do not survive configuration changes

I just tried it, they do survive. Do you mean that with the new method you don’t need a ViewModelProvider?

I just tried it, they do survive

That will come as a great shock to Google

 

Do you mean that with the new method you don’t need a ViewModelProvider?

Whether or not you need a ViewModelProvider depends on whether you need to have a hand in creating the instances – you create the provider, and the Jetpack invokes your provider when needed.

hahah 😆

I have not looked at the viewModel() function signature, but it would not surprise me if a ViewModelProvider is an optional parameter, akin to by viewModels() for activity/fragment viewmodels

Well, at least they survive the dark mode switch

 

In this particular example.

¯\_(ツ)_/¯

 

all I can tell you is how they are supposed to work

I have not looked at the viewModel() function signature, but it would not surprise me if a ViewModelProvider is an optional parameter, akin to by viewModels() for activity/fragment viewmodels

That’s what I thought too.

 

Last question, when should you use "Flow" rather than "muteableStateOf" in the ViewModel?

IMHO, MutableState maps more closely to StateFlow than it would Flow

 

however, in general, my guess is that you would use a Flow if something hands you a Flow

 

for example, if you are using Room, a @Query function can return a Flow

 

but they have not added any sort of room-compose library that might support a @Query returning a MutableState

 

in a viewmodel, SharedFlow might still be relevant for events (things you want consumed once)

 

but I suspect that a lot of developers will aim to use mutableStateOf() wherever possible, because once you get into a composable, you need to convert a Flow to a MutableState anyway

Very enlightening, thank you for your time Mark!

happy to help!

john left