2

I have a ViewModel that takes a string as an argument

class ComplimentIdeasViewModel(ideaCategory : String) : ViewModel()  {
    //some code here
}

What is the best way to initiate this ViewModel inside a composable fun without using a ViewModel factory and Hilt? A simple statement seems to achieve this inside a composable fun

@Composable
fun SampleComposableFun() {
    val compIdeasViewModel = remember { ComplimentIdeasViewModel("someCategory") }
}

There is no warning in Android studio when I try to do this, but this seems too easy to be true, I am able to do this without Dependency Injection and with a ViewModelFactory class. Am I missing something here?

Anudeep Ananth
  • 955
  • 1
  • 8
  • 30

3 Answers3

1

I've tried how you have written yours out and I had issues with screen rotation resetting the view model. I suspect you may too.

I was able to fix it by utilizing the the factory parameter on viewModel() for this, which worked well for me. See this answer on a similar question with example on how to use it: jetpack compose pass parameter to viewModel

jschlepp
  • 91
  • 5
0

This will not provide you the correct instance if viewmodel. See if you store some state in the viewmodel, then using the factory to initialise it is necessary to ensure that you get the same and latest copy of the viewmodel currently present. There is no error since the syntactic implementation is correct. I do not know of any way to do this because most of the times, you don't need to. Why don't you initialise it in the top-level container, like the activity? Then pass it down wherever necessary.

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
  • If I initialize a ViewModel factory inside a top-level activity then wouldn't the ViewModel be scoped to the activity? I want the ViewModel to be cleared when navigated away from this composable. My Activity hosts multiple composable so that may be a memory leak. – Anudeep Ananth Jul 01 '21 at 05:34
  • 1
    Oh! If that is the case, there is no problem in initialising it the same way as you have. Long as the creating Composable is the only one that needs to access the state it may modify, it is ok, but that urges one to say, do you think what you require here is really a viewmodel? I mean there purpose is to create consistency across activities and everywhere state needs to be accessed. If you want it to be cleared every once in a while, using variables is as good, and perhaps better – Richard Onslow Roper Jul 01 '21 at 05:47
0

Create a CompositionLocal for your ViewModel.

val YourViewModel = compositionLocalOf { YourViewModel(...) }

Then initialise it (You'd likely use the ViewModelProvider.Factory here). And then provide that to your app.

CompositionLocalProvider(
  YourViewModel provides yourInitialisedViewModel,
) {
  YourApp()
}

Then reference it in the composable.

@Composable
fun SampleComposableFun(
  compIdeasViewModel = YourViewModel.current
) {
  ... 
}

Note, the docs say that ViewModels are not a good fit for CompositionLocals because they will make your composable harder to test, make your composables tied to this app and make it harder to use @Preview.

Some get pretty angry about this. However, if you manage to mock out the ViewModel, so you can test the app and use @Preview and your composables are tied to the app and not generic, then I see no problem.

You can mock a ViewModel fairly simply, providing its dependencies are included as parameters (which is good practice anyway).

open class MockedViewModel : MyViewModel(
  app = Application(),
  someOtherDependeny = MockedDependecy(),
)

The more dependencies your ViewModel has the more mocking you'll need to do. But I've not found it prohibitive and including the ViewModel as a default parameter has massively sped up development.

mmm111mmm
  • 3,607
  • 4
  • 27
  • 44