
Kotlin delegates in Android development — Part 2
Architecture components: ViewModel and LiveData
At Google I/O 2017 there were two big announces for Android developers: official Kotlin support and Architecture Components. In the second part of this post (you can find the first part here) we’ll see how to use Kotlin delegates to simplify the integration of ViewModel and LiveData in an Android app.
ViewModel delegate
A ViewModel can be created using a ViewModelProvider.Factory
, an interface that contains a single method. This method creates (or reuses a previous one after a configuration change) an instance of a ViewModel based on the Class
parameter. The default implementation of this interface uses the reflection to create a new instance.
When the ViewModel is managed using Dagger a custom implementation of this factory must be defined. A single factory can be used to create all the ViewModels in a project, but this implementation will contain many if
to check the class parameter.
An alternative solution is to create a single factory for each ViewModel, a method that transforms a generic function to a ViewModelProvider.Factory
can be written easily:
inline fun <VM : ViewModel> viewModelFactory(
crossinline f: () -> VM) =
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(aClass: Class<T>) =
f() as T
}
This method can be used inside a standard lazy
delegate to create a ViewModel using an invocation to a components method (a Dagger Provider
field can be used as well):
private val viewModel by lazy {
val factory = viewModelFactory { component.myViewModel() }
ViewModelProviders.of(this, factory)
.get(MyViewModel::class.java)
}
In this example component
is an extension function that can be used to retrieve the Dagger component from the Application
, we defined it in the first part of this post. Using this method we can avoid writing a single factory for all the ViewModels in the project. Each Activity
(or Fragment
) contains its own factory created using the viewModelfactory
method with a lambda as argument that is used to create the ViewModel if needed.
The code can be simplified moving the factory creation to a custom delegate that contains an argument with the method to invoke:
inline fun <reified VM : ViewModel> Fragment.viewModelProvider(
crossinline provider: () -> VM) = lazy {
ViewModelProviders.of(this, object : ViewModelProvider.Factory {
override fun <T1 : ViewModel> create(aClass: Class<T1>) =
provider() as T1
}).get(VM::class.java)
}
The usage in an Activity
(or a Fragment
) is really simple, it just uses the viewModelProvider
delegate passing a lambda with the method that allows to create a new instance:
private val viewModel by viewModelProvider {
component.myViewModel()
}
Lazy delegate is thread safe by default, in this example the viewModel property will be used always from an Activity
/Fragment
. Using MVVM (or also MVP) these methods are usually lifecycle callbacks or view updates, in both cases they are always executed on the main thread so the extra synchronization is not required. To avoid this overhead we can add a parameter with a default value set to NONE
:
inline fun <reified VM : ViewModel> Fragment.viewModelProvider(
mode: LazyThreadSafetyMode = NONE,
crossinline provider: () -> VM) = lazy(mode) {
ViewModelProviders.of(this, object : ViewModelProvider.Factory {
override fun <T1 : ViewModel> create(aClass: Class<T1>) =
provider() as T1
}).get(VM::class.java)
}
LiveData
Another architecture component that is often used with a ViewModel is LiveData, you can find more details about it in this post written by Ian Lake and in many other posts.
A delegate can be used to simplify LiveData usage, we can write it in just a few lines of code:
class LiveDataDelegate<T: Any>(
initialState: T,
private val liveData: MutableLiveData<T> =
MutableLiveData<T>()
) { init {
liveData.value = initialState
} fun observe(owner: LifecycleOwner, observer: (T) -> Unit) =
liveData.observe(owner, Observer { observer(it!!) }) operator fun setValue(ref: Any, p: KProperty<*>, value: T) {
liveData.value = value
} operator fun getValue(ref: Any, p: KProperty<*>): T =
liveData.value!!
}
Here I am not using ReadWriteProperty
class but I am just defining two functions using the operator
keyword. To avoid null values in the LiveData
the parameter T
is defined as an Any
subclass and there is a constructor parameter with a not nullable initial state.
However this delegate is a bit different compared to a standard one because we need to invoke not only a getter/setter but also another method (the UI invokes observe
method to register itself as listener). The simplest way to solve this problem is using two fields in the ViewModel
class, one created using the delegate and the other with a LiveDataDelegate
object:
class MyViewModel : ViewModel() {
val liveData = LiveDataDelegate(0) private var state by liveData fun increment() {
state++
}
}
As in SharedPreferences example we are invoking ++
operator, under the hood we are now reading the value from the LiveData
and updating it.
In the view class (an Activity
or a Fragment
) we can observe the value invoking observe
method on the LiveDataDelegate
property:
viewModel.liveData.observe(this) {
textView.text = it.toString()
}
In this example the MyViewModel
class contains a single LiveData
, we can refactor it a bit to simplify the code (and to try another Kotlin feature!).
Let’s start extracting an interface out of LiveDataDelegate
class:
interface LiveDataObservable<out T> {
fun observe(owner: LifecycleOwner, observer: (T) -> Unit)
}
Now we can use Kotlin class delegation to define just a single field in the ViewModel
, this class implements LiveDataObservable
so the view will be able to invoke observe
method directly on the ViewModel:
class MyViewModel(
liveData: LiveDataDelegate<Int> = LiveDataDelegate(0)
) : ViewModel(), LiveDataObservable<Int> by liveData { private var state by liveData fun increment() {
state++
}
}
There is another way that allows to avoid the definition of an extra property, it’s described in this StackOverflow issue. However it uses the reflection so the Kotlin reflect library must be added to the project.
Wrapping up
Using the two delegates of this post we can write an Activity with a TextView
bound to a LiveData
value. Clicking on the TextView
the value is incremented, the view is automatically updated thanks to LiveData
:
class DemoActivity : LifecycleActivity() {
private val viewModel by viewModelProvider {
component.myViewModel()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val textView = TextView(this).apply {
textSize = 80 * resources.displayMetrics.density
gravity = CENTER
setOnClickListener { viewModel.increment() }
}
setContentView(textView,
LayoutParams(MATCH_PARENT, MATCH_PARENT))
viewModel.observe(this) {
textView.text = it.toString()
}
}
}
A demo project with some examples of these two delegates is available on GitHub.
Kotlin delegates are really powerful and can be used to simplify Android development. In these two post we have seen some examples, I hope I’ll find soon other examples to write another post about this subject (if you have any suggestions please leave a comment!).