Architecture Components: Hack the API by using Kotlin extensions
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
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 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:
1 | 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:
Want to learn Kotlin?
Check my free guide to create your first project in 15 minutes!
1234 | classNotificationsListViewModel:ViewModel(){val notificationsList=MutableLiveData<List<Notification>>()...} |
And then, you can observe the changes on that LiveData
by doing:
1 | 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:
123 | inline fun<reifiedT:ViewModel>getViewModel(activity:FragmentActivity):T{returnViewModelProviders.of(activity)[T::class.java]} |
And now, to use it:
12 | 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:
123 | inline fun<reifiedT:ViewModel>FragmentActivity.getViewModel():T{returnViewModelProviders.of(this)[T::class.java]} |
1 | 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:
123 | 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:
123 | withViewModel<NotificationsListViewModel>{notificationsList.observe(this@NotificationsListActivity,Observer(::updateUI))} |
Much easier to read! How is this implemented?
12345 | inline fun<reifiedT:ViewModel>FragmentActivity.withViewModel(body:T.()->Unit):T{val vm=getViewModel<T>()vm.body()returnvm} |
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:
1 | 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:
1 | 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:
123 | 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:
1 | observe(notificationsList,::updateUI) |
The result
So now, mixing all the pieces together, we’ve gone from this:
12 | val vm=ViewModelProviders.of(this)[NotificationsListViewModel::class.java]vm.notificationsList.observe(this,Observer(::updateUI)) |
To this:
123 | 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:
123456789 | val appPackage=intent.getStringExtra(APP_PACKAGE)val factory=object:ViewModelProvider.Factory{override fun<T:ViewModel?>create(modelClass:Class<T>):T{returnNotificationsListViewModel(appPackage)asT}}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