Before we jump into how to use ViewModel
’s in custom views, let’s take a look at why you would want to do something like this.
Let’s consider a screen with a list of views showing a bunch of data and having some state. While it’s entirely possible to handle this scenario at your screen level, you would be doing a bunch of things in your state management and it becomes harder to manage and test.
One way to simplify the state management of this screen is to extracting out it into a bunch of custom views that manage their state. (You don’t have to use a ViewModel
to do this, but this article focuses on Jetpack ViewModel)
Alright, now that we established some context. Let’s look at code now.
Creating a ViewModel
in the view
Great, you have extracted out the view from your screen to a custom view, and you have created a ViewModel
to accompany that. Now, all you have to do is use ViewModelProvider
to create/get the ViewModel
.
Here comes your first issue, to create/get a ViewModel
from the ViewModelProvider
you would need to provide access to the ViewModelStoreOwner
when constructing ViewModelProvider
, and View is not a ViewModelStoreOwner
.
So, you have 2 ways to get the store owner.
-
Implementing
ViewModelStoreOwner
interface in your custom view -
Getting nearest
ViewModelStoreOwner
Implementing ViewModelStoreOwner
interface in your custom view
I won’t go into too much detail about the first option, but I didn’t prefer this, since it requires us to implement the interface for every custom view where we want to use ViewModel
. You can abstract way this with a base class, but you would end up creating a bunch of base classes for different view group types.
Although, this option is good if you want to scope your ViewModel
lifecycle to the custom view and clear it once your view is destroyed. But for our use case, we will be placing these custom views inside a screen (fragment or activity) which is a ViewModelStoreOwner
, and want our ViewModel
’s to be scope to that screen.
Getting nearest ViewModelStoreOwner
In this approach, we would want to get the nearest ViewModelStoreOwner
which is usually an Activity,
Fragment
, or a custom store owner. So, how do we do that?
Let me introduce you to ViewTreeViewModelStoreOwner
, this allows us to get the nearest ViewModelStoreOwner
for a view. This checks the ancestors of the view to get the store owner. Which is exactly what we need.
Let’s take a look at an example:
You can use ViewTreeViewModelStoreOwner#get
to get the nearest ViewModelStoreOwner
and provide it when constructing the ViewModelProvider
.
private val viewModel by lazy {
ViewModelProvider(ViewTreeViewModelStoreOwner.get(this)!!).get<SummaryViewModel>()
}
If you’re using viewmodel-ktx
artifact, you can use findViewTreeViewModelStoreOwner
extension.
private val viewModel by lazy {
ViewModelProvider(findViewTreeViewModelStoreOwner()!!).get<SummaryViewModel>()
}
This will create/get the ViewModel
scoped to the screen ViewModel
lifecycle.
Important: Since ViewTreeViewModelStoreOwner
relies on getting store owner from the ancestors, make sure you are calling it in onAttachedToWindow
.
Bonus
Now you have your ViewModel
’s in your custom views, but how do you observe LiveData
or lifecycle-aware observers?
Well, again there are two options
-
Implementing
LifecycleOwner
interface in your custom view -
Getting nearest
LifecycleOwner
usingViewTreeLifecycleOwner
Both of these are good options depending on which lifecycle scope you would want your observer to observe.
Here is the full code for the custom view
class SummaryView(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) {
private val viewModel by lazy {
ViewModelProvider(findViewTreeViewModelStoreOwner()!!).get<SummaryViewModel>()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
viewModel.summaryModel.observe(findViewTreeLifecycleOwner()!!, ::populateSummaryView)
}
private fun populateSummaryView(summaryModel: SummaryModel) {
// do stuff
}
}
Well, that’s all folks. Until next time 👋🏾
Using ViewModels in custom views
Let's take a look at how we can use `ViewModel`'s in custom views using `ViewTreeViewModelStoreOwner`