Android different ways to create viewModel object which one to use when?
Asked Answered
D

2

16

I recently started with the ViewModel and AndroidViewModel, I see there are different approach to initialise a viewModel instance, for me all works fine, I just want to know which one to use when? and where should I initialise the viewModel object? following all are the different approach to get the viewModel instance and works for me:

    val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
    val myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)
    val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)
    val myViewModel4: MyViewModel by viewModels()
    val myViewModel5 by viewModels<MyViewModel>()

The easiest and most simple for me are 3rd, 4th and 5th, however I don't know what is the difference in all the five approaches, also please let me know if there any other way or optimal way to initialise my viewModel object, I do the initialisation on the global variable while declaring it, is it okay to initialise at the declaration time or it should be done inside some lifecycle method?

Depew answered 8/1, 2022 at 14:52 Comment(0)
D
28

In case anyone looking for in depth answer, please check this, here we have the following way to create or get the viewModel object:

  1. val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)

  2. myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)

  3. val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)

  4. val myViewModel4: MyViewModel by viewModels()

  5. val myViewModel5 by viewModels<MyViewModel>()

All do the same thing, the only two key differences is:

  1. The viewModel initialisation with lazy loading and without lazy loading.
  2. The viewModel with multiple parameter and no parameters.

Lets see this wrt the lazy loading and without lazy loading, the first three are without the delegate by that means there is no lazy loading of that object, so it's the developer responsibility to create the viewModel object only when activity is created or the fragment is attached to the activity, that means the first three approach(1, 2, 3) can't be used at global scope, if used at global scope the variable must be a var with lateint or null initialisation, and the initialisation(approach 1, 2, 3) must happen in the onCreate or onViewCreated(in case of fragment).

Therefor the best way to create the viewModel object is using the delegate by(4, 5), both are same with a bit different syntax, I choose 4 because of it's simplicity and readability.

val myViewModel4: MyViewModel by viewModels()

The by delegate gives the flexibility to lazy load the instance and you can define the viewModel at global scope and get ride off the boilerplate code, if you try to initialise the viewModel at global scope without the delegate the app will crash since the viewModel will try to initialise before the activity is created(it will not lazy load the viewModel instance).

Now let's see how to lazy load with multiple parameters, the 6th approach not mention in the question.

If you have multiple parameters in your view model and not using any dependency injection, you can use a ViewModelFactory implementation and then lazy load it:

val myViewModelWithParm: MyViewModel by viewModels { MyViewModelFactory(application, "param1", "param2") }

ViewModelFactory implementation:

    class MyViewModelFactory(val application: Application, val param1: String, val param2: String) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MyViewModel(application, param1, param2) as T
    }
}

Till this point we are clear on the delegate initialisation(4, 5), and how it is different with(1, 2, 3) now let's see the difference on the top 3 approach(1, 2, 3).

Let's first check 1 and 2.

  1. val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
  2. myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)

The key difference in them is one uses ViewModelProvider.NewInstanceFactory and other uses ViewModelProvider.AndroidViewModelFactory, so I checked the source code of both the classes and found that ViewModelProvider.AndroidViewModelFactory is actually the implementation of ViewModelProvider.NewInstanceFactory which override the create function that means both are doing the same stuff, preferable both approach should be chosen if we want multiple parameters however for that we have to override ViewModelProvider.NewInstanceFactory to create our own factory like it's done here

Now comes the third one:

val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)

This is the simple form of 1 and 2 when we don't have multiple parameters in our ViewModel and don't want to lazy load the object.

Note: I highly recommend the approach 4 or 5(both are same with different syntax), since this is the most suitable and optimal to write, if you don't have multiple arguments, in case you have multiple arguments you can use the approach 6 mentioned in the answer by implementing ViewModelProvider.Factory.

Depew answered 9/1, 2022 at 7:41 Comment(0)
C
2

3 is the standard way to fetch (and create if necessary) a ViewModel with no constructor parameters. Internally, that's doing 1 to pass a factory that calls the empty constructor (NewInstanceFactory()).

An AndroidViewModel is a subclass of ViewModel that automatically passes in an application reference, in case you need access to things like the application context. So even if your AndroidViewModel has no parameters, the factory that creates it needs to pass in an application, which is what 2 is doing.

This is all taken care of for you by default using 3 - you only need to define and use a factory if your VM needs to be configured with some extra parameters.


4 and 5 are the same thing, just with the type specified in a different place (you only need one declaration and the other will be inferred). They're delegates from the KTX libraries, and they do the same thing as 3, but they're much more readable IMO - especially if you're mixing scopes, like using by viewModels to get a Fragment's own VM, and also by activityViewModels to get the Activity's VM to share data with that and other Fragments.

They're also lazy delegates (as far as I'm aware!) meaning the VM only gets instantiated when it's first accessed, which is generally going to happen later in the lifecycle (instead of when the object is first constructed). I'm not sure if there is a problem initialising the VM on construction, but all of the official examples I've seen seem to fetch it in onCreate (or thereabouts)

Cornstarch answered 8/1, 2022 at 22:39 Comment(1)
Hi @Cornstarch , thanks for the answer and +1, to understand your point1, I took help from this link https://mcmap.net/q/135821/-android-viewmodel-additional-arguments, and for 2nd point, I understood that it should be used only when AndroidViewModel is used, correct me If I'm not, the 3rd point is 3rd approach can be used either places (where AndroidViewModel or ViewModel is extended), the 4th point I already have my answer as I tried testing it with different scope, with all approaches also tested with Activity & Fragment and come to conclusion val myViewModel4: MyViewModel by viewModels() is the beat way.Depew

© 2022 - 2024 — McMap. All rights reserved.