1

In the MainActivity I create a ViewModel object using:

class MainActivity : ComponentActivity() {
    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            if(viewModel.isActive()) {
                //Do stuff...
            } else {
                //Do other stuff...
            }
        }
    }
}

And inside a composable function, I'm injecting it using Hilt:

@Composable
fun ActivateScreen(
    viewModel: MainViewModel = hiltViewModel()
) {
    Scaffold(
        topBar = { /... },
        content = {
            UiContent(
                onClick = {
                    viewModel.activate()
                }
            )
        }
    )
}

But it seems to me that the view model objects are different objects, because when I click activate, the change is not propagated to the MainActivity. I need to restart the app in order to see the change. How to make sure I'm using the same instance? Any help would be greatly appreciated.

Joan P.
  • 2,368
  • 6
  • 30
  • 63
  • A quick way to see that is not the same is to place a breakpoint in the init block of the viewmodel, if the call inside mainactivity executes it and the call inside your composable does it , that will tell you that the two instances are different – Gastón Saillén Dec 21 '22 at 14:49
  • 2
    It seems like your `activate()` function is supposed to make `viewModel.isActive()` return true. Even if you have the same instance, calling `activate` will not trigger invocation of `isActive()`. Can you share what do your `activate` and `isActive` functions look like? – Arpit Shukla Dec 21 '22 at 14:55
  • I've provided an answer to what I believe your problem to actually be, though as @ArpitShukla points out, without knowing the implementation of your viewModel, it is difficult to know where the problem lies. – undermark5 Dec 21 '22 at 16:40

4 Answers4

1

A quick way to see that is not the same is to place a breakpoint in the init block of the viewmodel, if the call inside mainactivity executes it and the call inside your composable does it , that will tell you that the two instances are different. If you are already creating the viewmodel in your activity with hilt, just pass it as a parameter to your composable

class MainActivity : ComponentActivity() {
    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            if(viewModel.isActive()) {
                //Do stuff...
              ActivateScreen(viewmodel)
            } else {
                //Do other stuff...
            }
        }
    }
}

@Composable
fun ActivateScreen(
    viewModel: MainViewModel
) {
    Scaffold(
        topBar = { /... },
        content = {
            UiContent(
                onClick = {
                    viewModel.activate()
                }
            )
        }
    )
}
Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77
1

The reason why you are not seeing the change propagated to Main activity is perhaps not due to an issue of the instances of the viewModel being 2 different instances, but more than likely it is because Compose isn't triggering a recomposition because the value checked in MainActivity if(viewModel.isActive()) is not a state that Compose is aware of. In order for the recomposition to trigger, you will need to read a state that compose is aware of. One way to achieve this is to use mutableStateOf in the viewModel, then rather than having isActive be a function, it can be a val with a custom getter, or you can use the delegate form.

var isActive by mutableStateOf(false)

As for checking if the two are indeed the same instance, if you place a breakpoint in the code after each one is assigned, you can get the hashcode, which unless you've overridden hashCode it will default to be the memory address of the object, if both instances have the same memory address, they are the almost certainly the same instance. Another way would be to pass them around until you can use === to do a reference comparison rather than a equality comparison, which will tell if they are the same instance or not.

undermark5
  • 775
  • 6
  • 17
0

If you want to use Dagger Hilt you should do like this: (I also used a similar code in my project)

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var viewModel: MainViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        if(viewModel.isActive()) {
            //Do stuff...
          ActivateScreen(viewmodel)
        } else {
            //Do other stuff...
        }
    }
}

}

@Composable
fun ActivateScreen(
viewModel: MainViewModel
) {
Scaffold(
    topBar = { /... },
    content = {
        UiContent(
            onClick = {
                viewModel.activate()
            }
        )
    }
)

}

@HiltAndroidApp
class MyApp: Application()

And in Manifest.xml

<application
    android:name=".MyApp"
Adamo Branz
  • 164
  • 2
  • 7
0

Here is a small experiment:

val viewModel by viewModels<TestViewModel>()
val viewModel2 by viewModels<TestViewModel>()
val viewModel3 = TestViewModel()
val viewModel4 = ViewModelProvider(this@MainActivity).get(TestViewModel::class.java)
                        viewModel.vTest = "init"
Text(text = "In Activity " + viewModel.vTest + " " + viewModel.hashCode())
Text(text = "In Activity2 " + viewModel2.vTest + " " + viewModel2.hashCode())
Text(text = "In Activity3" + viewModel3.vTest + " " + viewModel3.hashCode())
Text(text = "In Activity4 " + viewModel4.vTest + " " + viewModel4.hashCode())
FunTest()

@Composable
fun FunTest(){
    val context= LocalContext.current as ComponentActivity
    val viewModel =ViewModelProvider(context).get(TestViewModel::class.java)
    Text(text = "In Function" + viewModel.vTest + " " + viewModel.hashCode(), modifier = Modifier.clickable {
        Toast.makeText(context,"click!",Toast.LENGTH_LONG).show()
    })
}

diferent way to get viewmodels

Outside the Activity,we need ViewModelProvider to get a stored ViewModel instance. If we directly call viewmodel constructor, we will get another new instance (like viewmodel3 in the code and the screenshot).

helvete
  • 2,455
  • 13
  • 33
  • 37
Null
  • 1
  • 1