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) {
}
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")
}
}
[Updated - Oct 30, 2021]: In fact, we can just implement the
ViewModelProvider.Factory
interface for bothMyViewModelFactory
andMyAndroidViewModelFactory
. 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())
}
[Updated - Nov 7, 2021]: you can also use
by lazy
andViewModelProvider()
instead ofby viewModels
and it should still work. It can't be used to replaceby activityViewModels
because the createdViewModel
won'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 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 Bundle
to 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)