Make your view-model properties great again
I’ve been experimenting with the android data-binding library for well over a year now, in that time I’ve learned a lot about how it works and how I think best to use it. I know there are some naysayers out there that believe data-binding is the root of all evil, but most of these claims can be quashed by adopting good practises, like every other tool or programming language that ever existed.
There’s alternative approaches to MVVM, such as using Butterknife or RxJava to bind the view and view-model together which are equally valid approaches, but I prefer data-binding as it allows us to easily write reusable glue code in the form of BindingAdapters that can be used from xml layouts. This reduces repetitive ‘GUI-code’ in the view layer, the main rationale behind MVVM when Microsoft conceived the pattern back in 2005!
There’s plenty of great articles out there for MVVM and data-binding, some even going as far to describe the differences between alternative patterns, but I’d like like to look at a topic that’s often glossed over: properties.
One of the common misconceptions I have seen with data-binding is how view-models should expose their properties. From the perspective of the view, properties have two main attributes, they can be read-only or read-write, and static or observable. Let’s draw it up:
Properties are not necessarily interfaces or abstract classes; I’ve used IntelliJ style colour coding here to emphasise that properties will ultimately fall into one of the four ‘concrete’ property types.
A read-only property is a property that the view-binding can read from to display some data in the view. To show an example of this, we have to look at the ‘concrete’ property types, the simplest being a static read-only property.
Static read-only properties
For want of a better word, a ‘static’ read-only property is a property that should not change during the lifetime of the binding between the view and view-model. In other words, if the property changes, the view-binding cannot be notified of the change and will never be reflected in the view, unless the view-model is manually rebound to the view. Lets take a look:
This is the simplest type of property; the view-model doesn’t need to extend
BaseObservable , implement
Observable , or wrap the properties in
ObservableFields or the primitive counterparts. As the backing field of the property is final, we have compile time safety that the view will never be out of sync with the view-model — given that the getter is not modifying the return value.
Although technically not properties going by the definition of the term, they can be exposed as public members. In this case they should always be
final, where the types should be immutable such as a
String, primitive, boxed primitive or your own immutable type. So to reduce the noise in the above code, it could be expressed as:
Luckily Kotlin supports the concept of properties out the box, so we can reduce the noise even further and take back our getters:
If the backing field of a getter is mutable, the getter should return a copy — The most common use-case being a mutable
List of some immutable type. In this case, the getter should return a copy of the
List with the elements of the original
List, so that the view layer cannot mutate the state of the view-model.
Observable read-only properties
Conversely, an observable read-only property is a property that can change during the lifetime of the binding between the view and view-model. This means that when the property changes, the view-binding needs to be notified of the change so that it can read the new value from the view-model and update the view.
There’s various ways of achieving this behaviour, some being more complicated than others, all of which are explained in depth in this blog post by George Mount.
[Side note: You’ll notice I’m using the new architecture components library for reacting on lifecycle events, and RxJava for our hypothetical use-case
getFollowerCount — they are not the main focus here though.]
The problem with this approach is that by using these convenience classes, you are inadvertently exposing a property setter for the view to potentially manipulate, thus making it a read-write property. This may not be a big deal unless your view-model exists as part of a library or standalone module, but you should think of a view-model’s public API as a contract for the view to fulfil — similar to how the MVP pattern works.
The problem we face now is that the view-model is already extending a base class — this might change to an interface in a future version of the architecture components library as it’s still in alpha. Even so, we may still need our own base class for whatever reason.
Ideally we want the best of both worlds, the freedom of implementing the
Observable interface, without the boilerplate of having to manage our own
PropertyChangeRegistry. We could make our own
ViewModel base class with a
PropertyChangeRegistry, but we may want to reuse this behaviour in view-models that don’t need to be retained across configuration changes.
[Side note: As of now the only real benefit of using the
ViewModel base class that I can see is if you want it to be retained across configuration changes easily. If your view-model is backing an item in a
RecyclerView, there is not much point in using it as they would exist in memory until the
Activity is finished, which may result in potentially hundreds of unused
ViewModels in memory. It would perhaps make more sense to move the
onCleared method to an interface and allow any object implementing that interface to be retained across configuration changes. As long as these objects are not holding strong references to an
Context, you can avoid leaking the previous
So instead of creating 2 base view-model classes with duplicated code, (one with configuration change behaviour and one without), we can use composition to share the implementation detail. We will do this with the delegation pattern, luckily Kotlin supports this (trust me you don’t want to see how this pattern scales in Java).
We can’t use the framework class
BaseObservable as it’s not designed for this, but we can implement our own version quite easily. First we have to extract some of the methods from the
BaseObservable class to an interface:
The two methods we need from
notifyChange. The extra method,
setDelegator is required due to an implementation detail of the base class of
notifyCallbacks method requires the actual
Observable instance that the generated
ViewDataBinding will bind to, but the BaseObservable implementation from the framework passes itself here which does not work with delegation.
[Side note: I’ve added
LifecycleObserver here as it has no methods anyway — it will hopefully disappear in the final version and rely solely on annotations.]
Here’s how the new base class looks:
Now we will create a view-model base class with composition — for now only an implementation based on the
Now we’re delegating the property change logic to a
A note on testing here, to test that certain properties are notified, you can simply spy the view-model that is being tested, then assert that
notifyPropertyChanged is called with the expected BR property ID.
At last, we can return to our view-model class and tidy things up:
The original goal was to avoid exposing a setter for our observable read-only property, which is exactly what the code above does — but it’s verbose, and not the type of code we want to repeat for every property setter.
Delegated properties are an extremely useful feature in Kotlin that allows us to write reusable getter and setter code that may otherwise feel like boilerplate.
We need a property delegate that does two things — performs an inequality check before a new value is assigned, and calls
notifyPropertyChanged if the check passes.
The Kotlin standard library provides an abstract base class called
ObservableProperty that invokes it’s
afterChange method each time the property value is changed. It’s typical use case can be seen in the
Delegates.observable method — also part of the standard library.
ObservableProperty does not perform an inequality check when a new value is assigned to the property, so we need to create our own class to add this functionality. It also needs to be able to call
NotifiableObservable.notifyPropertyChanged when a new distinct value is assigned. Our new property delegate that does just that looks like this:
Before we can use this nicely in our view-model, we should follow common practises with delegates and provide it via an extension function:
And finally use it in our view-model:
That looks much better, the inequality check and notification logic is nicely hidden away behind our
BindableProperty delegate. The setter is also private and is therefore still ‘read-only’ from the perspective of the view. The
Bindable annotation must be explicitly set on the getter of the property — this is needed when using property delegates.
But there’s one final thing we can do to improve the readability of the property. Wouldn’t it be nice if we could avoid having to explicitly pass
If you’re familiar with data-binding, you’re probably no stranger to being bombarded with error messages during compilation like:
‘cannot find symbol import your.app.package.BR’
This can happen when the data-binding expressions in xml are incorrect, but also when other annotation processors fail.
So now we have 2 reasons to remove the
BR reference in our view-model — to improve the readability and reduce noise when a compilation error occurs.
I’m a fan of compile-time safety, but let’s consider reflection for a moment.
Kotlin’s property delegates receive a
KProperty instance, where the
KProperty.name field is a
String that matches the name of the property in the view-model. This is useful, because when a property is annotated with
Bindable, data-binding generates a constant in the
BR class , where the name of the field matches the name of the view-model’s property.
KProperty.name field can be used with standard Java reflection to get the
integer value from the matching propertyId constant in the generated
As reflection is generally slow on Android, we need to be smart about how we approach this.
What we ultimately want is to be able to map the
String value of
KProperty.name to the
integer value for the propertyId — so we’ll need to create a map using Java reflection. We can either lazily create entries in this map on request of each new
KProperty.name value, or pre-cache them on app initialisation. Here’s a helper class that allows us to do either:
PropertyMapper class has a
bindableResourceClass which must be set once during the lifetime of the application. Optionally you can call
preCache to create the mapping upfront instead of lazily evaluating them.
To make use of this, our property delegate needs to resolve the
propertyId from the
PropertyMapper when it is instantiated, giving the
KProperty.name value of the property.
Our property delegate now has a
provideDelegate operator which allows us to do extra things when the delegate is being provided. Normally you would return a new delegate object here, but instead just return the current instance after setting the
var — this was previously a
val, but needs to be mutable unless
provideDelegate returns a new instance.
Also the extension function to provide this delegate needs fixed.
Now we can finally return to the view-model (for the last time I promise), and use our improved property delegate.
There are many ways to implement view-model properties, but it’s important to know when to use one approach over the other.
If a view-model has no properties that change while bound to the view, you don’t need to implement
If you want to ensure a property is read-only from the perspective of the view, you can do this with a public getter, a private setter and a private backing field, optionally using Kotlin property delegates to reduce some of the noise. With a bit of reflective magic, data-binding code can become cleaner — at the expense of compile-time safety and a small runtime penalty of using reflection.
Read-write properties are more complex because of their bi-directional nature, I will hopefully explain these in another post.
If you want to try the
bindable delegate out, check out my toolbox library here. Note that you do not need to pass a
BR class to the
PropertyMapper, this is now handled automatically.