Make your view-model properties great again

@Uxmal, Mexico

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.

View-Model 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.

Read-only properties

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.

You may be tempted to use one of the convenience classes such as ObservableField or ObservableInt, such as:

[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.

To fix this, we need to ditch these convenience classes and either implement Observable or extend BaseObservable.

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 ViewModels 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 Activity Context, you can avoid leaking the previousActivity. ]

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 BaseObservable are notifyPropertyChanged and notifyChange. The extra method, setDelegator is required due to an implementation detail of the base class of PropertyChangeRegistry, CallbackRegistry; the 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:

This is very similar to the framework version’s implementation — the main difference being that when notifying the PropertyChangeRegistry of changes, it passes the delegator to it instead of itself.

Now we will create a view-model base class with composition — for now only an implementation based on the ViewModel class:

Now we’re delegating the property change logic to a BaseNotifiableObservable instance.

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.

Instead of throwing in the towel and reverting to an ObservableInt, we can tidy it up using delegated properties.

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 BR.followerCount?

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.

This KProperty.name field can be used with standard Java reflection to get the integer value from the matching propertyId constant in the generated BR class.

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:

The PropertyMapper class has a lateinit field 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 propertyId 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.

Conclusion

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 Observable.

If a view-model has properties that change, only those properties need to be wrapped in container objects like ObservableField, or exposed as Bindable properties on a Observable view-model.

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.

Stay tuned!

Update

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.

Android developer and Kotlin enthusiast.