Vincent Tsen
Android Kotlin Weekly

Android Kotlin Weekly

Recommended Ways To Create ViewModel or AndroidViewModel

Kotlin examples to show different ViewModel and AndroidViewModel implementations

Vincent Tsen's photo
Vincent Tsen
·Sep 18, 2021·

5 min read

Recommended Ways To Create ViewModel or AndroidViewModel

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

  • Manual Creation - Don't do this!
  • lateinit var with ViewModelProvider
  • by lazy with ViewModelProvider
  • by viewModels / activityViewModels
  • by viewModels (Custom Constructor Parameter)
  • My Common Practices

There are few ways to create ViewModel and AndroidViewModel. This article shows you the Kotlin examples of creating them.

This is an example of ViewModel or AndroidViewModel class that you may have.

class MyViewModel: ViewModel() {
}
class MyAndroidViewModel (app: Application)
    : AndroidViewModel(app) {
}

The code examples here are used in fragment class. So it may not work in the activity class. Small modifications are required if you copy and paste them into your activity class.

If you're not familiar Kotlin, you can go through some quick examples here first to understand some important concepts such as "Delegation".

Manual Creation - Don't do this!

private val viewModel = MyViewModel()
private val androidViewModel = 
    MyAndroidViewModel(requireActivity().application)

This works only if you don't rotate your phone. When you rotate your phone, an activity or fragment is destroyed and recreated. A new instance of ViewModel or AndroidViewModel is created again. So all the data before the screen rotation is lost. This defeats the purpose of ViewModel architecture. You want ViewModel to survive through activity or fragment destruction.

I made this mistake because I did not understand the reason of usingViewModelProvider to create ViewModel

lateinit var with ViewModelProvider

private lateinit var viewModel: MyViewModel
private lateinit var androidViewModel: MyAndroidViewModel
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {

    viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
    androidViewModel =          
        ViewModelProvider(this).get(MyAndroidViewModel::class.java)
}

Using ViewModelProvider is the right way to create ViewModel. When the activity or fragment is created, ViewModelProvider is smart enough to figure out to reuse the first created ViewModel instance.

If ViewModel doesn't change (which is likely true), using val Kotlin variable is a better option here.

by lazy with ViewModelProvider

To use val variable, you use by lazy property initialization. The delegated block gets executed when the variable is first accessed.

private val viewModel: MyViewModel by lazy {
    ViewModelProvider(this).get(MyViewModel::class.java)
}

private val androidViewModel: MyAndroidViewModel by lazy {
    ViewModelProvider(this).get(MyAndroidViewModel::class.java)
}

The code looks a lot cleaner than lateinit var solution. However, another elegant way is to use by viewModels or by activityViewModels.

by viewModels / activityViewModels

To use this Property Delegation, the following dependency needs to be added to the build.gradle (module-level). The version is just an example, you can use later or latest version.

implementation 'androidx.fragment:fragment-ktx:1.3.6'

The following code is awesome! It essentially does the same thing as by lazy without the need to specify the ViewModelProvider. It automatically figures out that for you.

private val viewModel: MyViewModel by viewModels()
private val androidViewModel: MyAndroidViewModel by viewModels()

If you want to share your ViewModel across different fragments within the same activity. You can use by activityViewModels.

private val viewModel: MyViewModel by activityViewModels()
private val androidViewModel: MyAndroidViewModel 
    by activityViewModels()

by viewModels (Custom Constructor Parameter)

It is very common to pass additional objects to the ViewModel constructor. The following example is passing Repository object into the MyViewModel and MyAndroidViewModel.

class MyViewModel(private val repository: Repository)
    : ViewModel() {
}

class MyAndroidViewModel(app: Application, repository: Repository)
    : AndroidViewModel(app) {
}

Having a custom constructor parameter for ViewModel is a bit complicated. You need to have a custom ViewModel factory to create your ViewModel.

To create your custom ViewModel factory, you can inherit from ViewModelProvider.NewInstanceFactory.

class MyViewModelFactory(private val repository: Repository)
    : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {

        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            return MyViewModel(repository) as T
        }

        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

For custom AndroidViewModel factory, you can inherit from ViewModelProvider.AndroidViewModelFactory

class MyAndroidViewModelFactory(
    private val app: Application,
    private val repository: Repository)
    : ViewModelProvider.AndroidViewModelFactory(app) {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {

        if (modelClass.isAssignableFrom(
                MyAndroidViewModel::class.java)) {

            return MyAndroidViewModel(app, repository) as T
        }

        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

Update: 10/30/2021: In fact, we can just implement the ViewModelProvider.Factoryinterface for both MyViewModelFactory and MyAndroidViewModelFactory. Examples can be found here.

To create ViewModel with your custom constructor parameter, you use by viewModels delegate property.

private val viewModel: MyViewModel by viewModels {

    MyViewModelFactory(Repository())
}

private val androidViewModel: MyAndroidViewModel by viewModels {

    MyAndroidViewModelFactory(
        requireActivity().application,
        Repository())
}

You can replace by viewModels with by ActivityViewModels if you want your ViewModel to survive in different fragments within the same activity.

private val viewModel: MyAndroidViewModel by activityViewModels {

    MyViewModelFactory(Repository())
}

private val androidViewModel: MyAndroidViewModel 
    by activityViewModels {

        MyAndroidViewModelFactory(
            requireActivity().application,
            Repository())
    }

Update: 11/7/2021: you can also use by lazy and ViewModelProvider() instead of by viewModels and it should still work. It can't be used to replace by activityViewModels because the created ViewModelwon't be shared across different fragments. So this is just for your reference and knowledge.

private val viewModel: MyViewModel by lazy {
    val factory = MyViewModelFactory(Repository())

    ViewModelProvider(this, factory).get(MyAndroidViewModel::class.java)
}
private val viewModel: MyAndroidViewModel by lazy {
    val factory = MyAndroidViewModelFactory(
        requireActivity().application, 
        Repository())

    ViewModelProvider(this, factory).get(MyAndroidViewModel::class.java)
}

My Common Practices

The fun thing about programming is there are many ways to do the same thing. Understand the differences, make you a better programmer.

I use the last method by default because I usually have custom constructor parameters in my ViewModel.

Also, I usually use by activityViewModels instead of by viewModels which allows me to share data across different fragments. It saves my time to figure out how to pass data to different fragments. For example, using Bundleto share data between fragments.

I also use AndroidViewModel by default instead of ViewModel because I usually need to access string resources and system services from the Application context. There are drawbacks being discussed over the internet, but I do not fully understand this part yet. For now, AndroidViewModel is good for me.

These are my common practices. I'm not sure other Android developers agree with me. Let me know your thoughts.

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this