· 7 min read

Hacking Architecture Components by using Kotlin

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 a LifecycleOwner. 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!

    Share:
    Back to Blog