DEV Community

Cover image for Recommended Ways To Create ViewModel or AndroidViewModel
Vincent Tsen
Vincent Tsen

Posted on • Edited on • Originally published at vtsen.hashnode.dev

Recommended Ways To Create ViewModel or AndroidViewModel

Kotlin examples to show different ViewModel and AndroidViewModel implementations

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) {
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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) {
}
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

[Updated - Oct 30, 2021]: In fact, we can just implement the ViewModelProvider.Factory interface 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())
}
Enter fullscreen mode Exit fullscreen mode

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())
    }
Enter fullscreen mode Exit fullscreen mode

[Updated - Nov 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)
}
Enter fullscreen mode Exit fullscreen mode
private val viewModel: MyAndroidViewModel by lazy {
    val factory = MyAndroidViewModelFactory(
        requireActivity().application, 
        Repository())

    ViewModelProvider(this, factory).get(MyAndroidViewModel::class.java)
}
Enter fullscreen mode Exit fullscreen mode

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 viewModels (Custom Constructor Parameter) 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.

[Updated - July 15, 2022]: I managed to try hilt to inject the dependencies ito view model. Here is the example:


Originally published at https://vtsen.hashnode.dev.

Top comments (0)