0

Inside my fragment class, when I am getting my viewModel, I can write my code in two different ways.

  • Using "viewModelStore"
ViewModelProvider(viewModelStore, viewModelFactory).get(FragmentViewModel::class.java)
  • Using "this"
ViewModelProvider(this, viewModelFactory).get(FragmentViewModel::class.java)

My question is, does any difference exist between the two alternatives, and if yes which one is the preferable approach?

Stylianos Gakis
  • 862
  • 8
  • 19

1 Answers1

0

If you're providing your own ViewModelProvider.Factory, then there's no difference, so just use what is easier, this.

Of course, if you're in Kotlin, you don't need to use ViewModelProvider directly at all, you'd instead want to use Fragment KTX and use

val viewModel: FragmentViewModel by viewModels { viewModelFactory }

Note that if you were not using your own factory, you should always pass in a ViewModelStoreOwner (i.e., this) instead of just passing in the ViewModelStore since the Javadoc explicitly mentions:

This method will use the default factory if the owner implements HasDefaultViewModelProviderFactory. Otherwise, a ViewModelProvider.NewInstanceFactory will be used.

The ViewModelStore constructor is not able to get the correct default factory.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • Thank you, very nicely explained, I think I understand it now, I do not have any reason to use viewModelStore and in the case when I do not provide my own Factory it is even wrong. Therefore I will just never use it in this case. For passing the viewModelFactory directly in the `by viewModels` I assume that it would be expected that the factory will have to be made before-hands, so some sort of Dependency Injection would have to take place right? – Stylianos Gakis Mar 10 '20 at 09:29
  • Injecting a factory is certainly a common use case (say, to provide mock ViewModels when testing), but isn't a requirement to use a custom factory - you could use it just to pass a few particular variables into your ViewModel's constructor – ianhanniballake Mar 10 '20 at 14:07
  • I do just that, using a factory to pass some variable to my ViewModel, however I was mostly asking that inside my ViewModel currently I can not just call `viewModels { viewModelFactory }` because that factory does not exist at that point. I create an object of it inside the fragment's onCreate method, therefore I don't have access to the factory at the top level of the class. So I said, in order to do that, I would expect that it would have to be injected with DI instead. If not, how would I have access not the factory object at that point? – Stylianos Gakis Mar 10 '20 at 16:13
  • The reason it is in a lambda (the `{ }`) is that it doesn't evaluate the lambda until you access the ViewModel for the first time. That means it works great with a `lateinit var` factory. – ianhanniballake Mar 10 '20 at 16:15
  • Oh wow, I did not know that either. Thank you so much for all the information! I have now tested it by creating my factory as a val but it is created `by lazy` and then I am using that factory inside the `by viewModels` as you suggested and it works just fine. I do not know if this is considered as good practice, but my onCreateView is much cleaner and readable in my eyes. – Stylianos Gakis Mar 10 '20 at 18:24
  • Yeah, using `by lazy` is great if you are reusing the same factory for multiple ViewModels (where creating just one instance would be enough). If you're only using the factory for one ViewModel, then just putting it in the block of `by viewModels` (and skipping the separately `by lazy`) is also an option, since it is effectively lazy anyways :) – ianhanniballake Mar 10 '20 at 18:26
  • You are just an endless stream of knowledge. Once again, thank you for taking your time to help me out. I appreciate it! As I am using the factory just for this one ViewModel I put the factory creation in there as well. The code now looks so much better and I understand it more as well. I hope this helps other people with the same questions as well. – Stylianos Gakis Mar 10 '20 at 18:51
  • @ianhanniballake how can i use the method 'by viewmodels()' with a factory ? – Marlon López Oct 12 '20 at 02:42
  • @MarlonLópez - that's exactly what the code in the answer does: `by viewModels { viewModelFactory }` – ianhanniballake Oct 12 '20 at 04:23
  • @ianhanniballake mm ok, what about the testing part, should i customize or mock the factory or a viewmodel attribute/argument such as the data source, i donnow how to combine the mocking part with the ui testing part :S – Marlon López Oct 12 '20 at 04:52
  • @MarlonLópez - generally, you'd want to inject your factory, which would allow you to provide a test specific factory that returns a mock ViewModel. – ianhanniballake Oct 12 '20 at 05:38