I’ve been using Architecture Components for a while, and I must admit I love them. The Android team has managed to find a way to let us forget about lifecycles and focus on what really matters.
But not only that. Thanks to Architecture Components (which you can learn more about them here), not committing mistakes becomes much easier. Just follow the rule of not using activities, views, contexts… from the ViewModels, and you are good to go: unexpected crashes during configuration changes will disappear.
And this also has another implicit benefit: your code becomes much easier to test. You get rid of the components that are more complicated to test and, if you use LiveData to communicate back to the activity, you can subscribe to them during tests and simulate all possible combinations. There’s a nice article about testing Architecture Components by Joe Birch.
A quick intro to Architecture Components
I don’t want to dive too deep into it, but to see how the rest of the code works, you need to have a little understanding of some concepts. In this article, I’ll only talk about two components:
- ViewModel: Is the one that allows abstracting from the activity lifecycle. It’s usually created when the activity is created, and dies when the activity finishes. If the activity needs to be recreated (a rotation for instance), the ViewModel will be kept alive.
- LiveData: it’s an observable component. You subscribe to the changes and receive updates when its value is modified. As the LiveData is lifecycle aware too, you don’t need to unsuscribe.
So, if the activity is using a ViewModel, it needs to recover it like this:
val vm = ViewModelProviders.of(this)[NotificationsListViewModel::class.java]
That class of architecture components will check if the ViewModel exists, and otherwise it will create a new instance and return it.
The ViewModel would be something like this:
class NotificationsListViewModel : ViewModel() { val notificationsList = MutableLiveData<List<Notification>>() ... }
And then, you can observe the changes on that LiveData
by doing:
vm.notificationsList.observe(this, Observer(::updateUI))
When the LiveData
changes (by setting a new value to the value
property, or calling postValue
), the updateUI
function will be called. Here I’m using a function reference.
A couple of extra random ideas:
ViewModelProviders.of
function accepts both an activity or a fragment.LiveData
needs to be observed by aLifecycleOwner
. You can implement that interface, or just use activities and fragments from the support library, which already implement it for you.
Cleaning up the use of Architecture Components
By making use of Kotlin features, we can convert the code above into something simpler and nicer.
The ViewModel
First thing that is a little convoluted is the way we recover the ViewModel
. We need to call a static method and then pass the class of the ViewModel
we want to use. This second point can give us a clue of what we may need. Remember reified types? With them, we can use the class of the generic type inside a function. So that will fit perfectly here:
We can do something like this:
inline fun <reified T : ViewModel> getViewModel(activity: FragmentActivity): T { return ViewModelProviders.of(activity)[T::class.java] }
And now, to use it:
val vm = getViewModel<NotificationsListViewModel>(this) vm.notificationsList.observe(this, Observer(::updateUI))
We’ve improved this a little bit, but there’s still some room for improvement. As you see here, we are passing this
to the function to refer to the activity. Why not using an extension function instead? That way, activities would have a new function to recover the ViewModel
directly:
inline fun <reified T : ViewModel> FragmentActivity.getViewModel(): T { return ViewModelProviders.of(this)[T::class.java] }
val vm = getViewModel<NotificationsListViewModel>()
Much nicer! The thing is, that most times, when we get a ViewModel
will be to do something with it. For instance, subscribing to some LiveData
properties. We can make use of the ninja functions, and do:
with(getViewModel<NotificationsListViewModel>()){ notificationsList.observe(this@NotificationsListActivity, Observer(::updateUI)) }
But why don’t we avoid that step? If you use Architecture Components in your App, you will probably do it for all your activities, so it makes sense to create your own function with receiver:
withViewModel<NotificationsListViewModel> { notificationsList.observe(this@NotificationsListActivity, Observer(::updateUI)) }
Much easier to read! How is this implemented?
inline fun <reified T : ViewModel> FragmentActivity.withViewModel(body: T.() -> Unit): T { val vm = getViewModel<T>() vm.body() return vm }
It just uses the getViewModel
and calls the body
function, which behaves as an extension function of the generic type, so it can be called by the ViewModel
. This is pretty awesome, right?
The LiveData
Everything starts looking great, but there’s still one line that looks pretty busy:
notificationsList.observe(this@NotificationsListActivity, Observer(::updateUI))
But why don’t we think about it the other way round? In fact, it’s the activity who is observing the notificationsList
, so the original implementation is a little misleading in naming. We would want something like:
activity.observe(notificationsList) { /* Do something when value changes */ }
Again, thanks to extension functions we can do this quite easily. As you may remember, LiveData
must be observed by a LifecycleOwner
. So let’s make an extension function for it:
fun <T : Any, L : LiveData<T>> LifecycleOwner.observe(liveData: L, body: (T?) -> Unit) { liveData.observe(this, Observer(body)) }
We are hiding the ugly code inside the function, and creating a nice API for it:
observe(notificationsList, ::updateUI)
The result
So now, mixing all the pieces together, we’ve gone from this:
val vm = ViewModelProviders.of(this)[NotificationsListViewModel::class.java] vm.notificationsList.observe(this, Observer(::updateUI))
To this:
withViewModel<NotificationsListViewModel> { observe(notificationsList, ::updateUI) }
It’s not a huge deal, but if we’re repeating this on all activities it’s nice having this functions that make the code cleaner and easier to use.
Extra: ViewModels with constructor arguments
I must admit I’ve been following the easy path here. Regular ViewModels are easy to use when they don’t receive any arguments. But things become more complicated if those ViewModels need arguments.
Imagine that the activity is receiving some info through its intent, and this needs to be used by the ViewModel. You could write this code:
val appPackage = intent.getStringExtra(APP_PACKAGE) val factory = object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { return NotificationsListViewModel(appPackage) as T } } val viewModel = ViewModelProviders.of(this, factory)[NotificationsListViewModel::class.java]
Pretty complex, right? I’m sure you think we can do it better. In fact, the factory is just a class with one method, so this sounds quite a lot like a lambda 🤔. So why don’t we create a new set of functions that just support a lambda that represents the factory?
The withViewModel
one is simple. It gets an extra lambda that returns the generated object:
inline fun <reified T : ViewModel> FragmentActivity.withViewModel( crossinline factory: () -> T, body: T.() -> Unit ): T { val vm = getViewModel(factory) vm.body() return vm }
The getViewModel
now will wrap the instance of the factory, but based on the previous code it’s not very difficult either:
inline fun <reified T : ViewModel> FragmentActivity.getViewModel(crossinline factory: () -> T): T { val vmFactory = object : ViewModelProvider.Factory { override fun <U : ViewModel> create(modelClass: Class<U>): U = factory() as U } return ViewModelProviders.of(this, vmFactory)[T::class.java] }
The generics part is a bit more convoluted, and I’m not totally sure that I got the best simplest solution, but it works and it just needs to be written once, so it can be enough.
With that, we are creating a factory object that will use the lambda to return the proper value. After all this nasty work, we can do things much cleaner:
val appPackage = intent.getStringExtra(APP_PACKAGE) withViewModel({ NotificationsListViewModel(appPackage) }) { observe(notificationsList, ::updateUI) }
Quite cool, right?
Conclusion
As I’ve mentioned several times, thanks to extension functions we can make the interaction with the framework much simpler and easier to read, by encapsulating all the boilerplate inside them.
In this case, I covered some situations I found when working with basic Architecture Components, but you can imagine that this can be applied to virtually any pain points of framework APIs.
Have you already found places where this kind of code made your life easier? Have you come up with other extension functions to deal with Architecture Components code? Feel free to let me know in the comments!
Hello!
That’s a very neat approach you’ve got there!
But I’ve got one question: how would you replace your view model factory for testing?
The provider is just a function here, so you can provide it from anywhere. Dagger can, or keep a providers file that itβs different depending on the Gradle variantβ¦ Whatever you usually use to provide dependencies in your tests
I like Room nad Lifecycle components, but LiveData and ViewModel are not worth investing too much time IMHO. LiveData is just a subset of RxJava functionality, which is very widely adopted already. Not really sure why Google didn’t push to make RxJava more “Android native” rather than providing own implementation of the same functionality. And ViewModel are something really strange for me. First, it has totally wrong name.. it is not ViewModel. It is basically an object connected to Activity/Fragment lifecycle and that’s it. It doesn’t provide any functionality. It has one callback π It could be used for whatever reason developer would like to have object bounded to Activity/Fragment lifecycle. It could be Presenter from MVP. Giving “ViewModel” name for that class is a little bit misleading.. but I guess Google wanted devs to use that only for ViewModel from MVVM pattern. But I feel like they would say to me “now we are advocating and giving you hint how your application should be architected. We are providing you useless class called ViewModel, so you know now you should use MVVM” π And saying the class is useless I mean you still need to manage saving/restoring state. The only benefit is that app might be a little bit faster on orientation changes, but at cost of additional dependency and more complex state management. If we add into that how much more complex it is getting when we put Dagger into the scene… I am just not convinced everything is worth it π
Congrats on your coming baby π That’s gonna be a completely different journey than the dev track.. believe me π
I think they’re pretty useful to start architecting Android Apps. It’s true that the same can be done with RX, but the learning curve is far different. Thanks VM is a VM if you use it in combination with LiveData (if I understood correctly the difference between MVP and MVVM ). And both combined are great to avoid lifecycle complexity. In general it’s a light reactive framework that also simplifies the interaction with Android lifecycle. Not saying it’s the best solution for everyone, but I think it can help architect Android Apps in not so experienced teams.
You probably gonna use Dagger anyway, so most stright method would be to inject factory into the activity. There is lot of tutorials, guides how to do that. Just search for “android dagger viewmodel” π I am personaly not a fan of ViewModel, as I dont see much advantages in using them, and setup with dagger is getting a little bit complex π
Nice! I use lazy for fetching the ViewModel, as I also want to use a custom ViewModelProvider.Factory that can do injection with Dagger:
inline fun FragmentActivity.lazyViewModel(): Lazy = lazy {
val factory = (this.application as MyApp).appComponent().viewModelFactory()
ViewModelProviders.of(this, factory).get(T::class.java)
}
Good idea! In case it helps, I usually also implement an extension property to get the application casted. Something like:
val Activity.app: MyApp get() = application as MyApp
Hi, do you have the code for the example
Hey! I don’t have anything that’s worth sharing. It’s an unfinished example. But if it helps, I can think about trying to finish something and create a repo.
ok, it would be very helpful
Hello,
There is a problem with this implementation.
Let’s take a look at this simple code:
val viewModelFizz = withViewModel({ NotificationsListViewModel(“fizz”) })
viewModelFizz.appPackage // “fizz”
val viewModelBuzz = withViewModel({ NotificationsListViewModel(“buzz”) })
viewModelBuzz.appPackage // “fizz”
As you can see, viewModel2.appPackage is returning fizz, but we were expecting buzz. This is because viewModelFizz will be stored and returned when you try to create and access a new instance of viewModelBuzz. ViewModelProviders use class name as a key to store ViewModel, so it will ignore what goes on in ViewModel constructors.
A simple workaround would be to specify an explicit key when trying to access a ViewModel via ViewModelProviders.
Example:
ViewModelProviders.of(this, factory).get(key, T::class.java)
Yeah, but that would happen with the long implementation of the factory too. The VM is unique while the activity is alive, so arguments of the constructor should not change during the lifetime of the activity. If they change, you can have a regular setter instead. I you do what you mention, the VM will be different, which goes against the idea of the VM itself.
I can’t seem to get observe extension function to work with AppCompatActivity. Says it can’t infer the type. Any help would be appreciated!
Hey Brandon. AppCompatActivity only extends LifecycleOwner since support 26.1. Maybe you’re using an older one?
You said yourself that the choice of the name “ViewModel” is horrible, don’t let that fool you.
The ViewModel doesn’t have to be unique. It can be used in a variety of different way, and having multiple instance of the same type of ViewModel with different arguments is one of this way.
If you want to provide full support to the existing ViewModel API with Kotlin’s function extension, here is a more modular approach, which is basically what you did, but more respectful of the different steps, and covering every use case:
inline fun FragmentActivity.viewModel(): T {
return ViewModelProviders.of(this).get(T::class.java)
}
inline fun FragmentActivity.viewModel(key: String): T {
return ViewModelProviders.of(this).get(key, T::class.java)
}
inline fun FragmentActivity.viewModel(factory: ViewModelProvider.Factory): T {
return ViewModelProviders.of(this, factory).get(T::class.java)
}
inline fun FragmentActivity.viewModel(key: String, factory: ViewModelProvider.Factory): T {
return ViewModelProviders.of(this, factory).get(key, T::class.java)
}
@Suppress(“unchecked_cast”)
inline fun viewModelFactory(crossinline factory: () -> T): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun create(modelClass: Class): U {
return factory() as U
}
}
}
Then you can use it this way in general:
viewModel { viewModelFactory { MyViewModel() } }
Or illustrating my fizz buzz example:
val factoryFizz = viewModelFactory { MyViewModel(“fizz”) }
val factoryBuzz = viewModelFactory { MyViewModel(“buzz”) }
val fizz = viewModel(“fizz”, factoryFizz)
val buzz = viewModel(“buzz”, factoryBuzz)
Ok, then there may be that I don’t get where you would need it. Why would you have two VMs for the same activity/fragment doing almost the same? But yeah, in the case you need it, your code would cover it. I haven’t found myself with that use case yet though.
Hello.
Great article!
I didn’t understand one thing. What is the meaning of the brackets in the next code piece:
inline fun FragmentActivity.getViewModel(): T {
return ViewModelProviders.of(this)[T::class.java]
}
`ViewModelProviders.of()` returns an object that has a `get` method in Java. This is automatically mapped in Kotlin to an indexed accessor, whose operator is `[ ]`. You can know more about it here https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
Had to change the type parameters to accept a nullable value in the live data:
fun <T : Any?, L : LiveData> LifecycleOwner.observe(liveData: L, body: (T?) -> Unit) {
liveData.observe(this, Observer(body))
}
Thanks! Yeah, I didn’t use that case in my sample App, so could be required.
how do you will edit the Fragment viewModel provider extension so you could share the same viewModel
So that the fragments can get the viewModel of the parent activity you mean? An extension applied to the fragment could access the associated activity, and the rest would be the same.