The ugly OnPropertyChangedCallback

Danny Preussler
ProAndroidDev
Published in
3 min readJan 25, 2018

--

https://www.flickr.com/photos/hpeake/28395138972

Data binding is an amazing feature in Android development. We can bind view model properties directly to the XML, the place where they are needed. No ugly ‘handwritten’ glue code in between, which also means less code in the Activities and Fragments.

But sometimes we need to intervene manually. Our team saw this a lot when handling with the leanback library on Android TV for example. It’s not that easy to use data binding there. Going into details would fill a whole series of blog posts I guess.

Anyway in some of those cases some part of the code manually needs to watch for changes on observable fields. An example can be even seen in the Google architecture blueprints about data binding (converted to Kotlin for the article):

snackbarCallback = object : Observable.OnPropertyChangedCallback() {
fun onPropertyChanged(observable: Observable, i: Int) {
showSnackbar(view, viewModel.getSnackbarText())
}
}

On every change of the property observed, this code shows a Snackbar.

The text that changes is retrieved from the view model. What if the text would be part of the Observable itself that we are watching, it could be a simple ObservableField<String>. We would not need another reference to the view model right? We would just call get() on the Observable.

But this callback was not done with generics, for hopefully some good reasons. But for us unfortunate developers this means code like this:

showSnackbar(view, (observable as ObservableField<String>).get())

That is some ugly code, isn’t it?

So, lets try to make it better:

First idea is through an intermediate class:

abstract class TypedOnPropertyChangedCallback<T: Observable> : 
Observable.OnPropertyChangedCallback() {

abstract fun onPropertyChange(observable: T?, i: Int)

override fun onPropertyChanged(observable: Observable?, i: Int) =
onPropertyChange(observable as T, i)
}

so we would duplicate the method (with slightly different name) and expose the new generic version.

But this is very Java’ish, let’s use the power of Kotlin:

fun <T: Observable> T.addOnPropertyChanged(callback: (T) -> Unit) =
addOnPropertyChangedCallback(
object: Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(
observable: Observable?, i: Int) =
callback(observable as T)
})

So we define an extension function on any generic Observable, with the same name as the original method, that would receive a lambda (let’s assume that we, as in this case above, don’t care about the 2nd parameter). This way we also don’t need a different name

The usage becomes super easy:

vm.snackbarText.addOnPropertyChanged({showSnackbar(view, it.get()})

Well done, you might think.

This solution, although super nice has one drawback. If you use architecture component view models the view model lives longer than the UI, so you need to unregister. So we still need to keep a reference to the listener for later unregistration. Please keep this in mind when passing around lambdas, it’s easy to create memory leaks.

But a change is easy. All we need to do is return the callback, and Kotlin allows us this very easily, by reverting the way we write it:

fun <T: Observable> T.addOnPropertyChanged(callback: (T) -> Unit) =
object: Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(observable: Observable?, i: Int) =
callback(observable as T)
}.also { addOnPropertyChangedCallback(it) }

Usage is still the same but we can keep the reference:

snackbarCallback = vm.snackbarText.addOnPropertyChanged({
showSnackbar(view, it.get()})

One thing that RxJava did really well was the idea of Disposable. So we could instead of returning the callback, return a disposable.

fun <T: Observable> T.addOnPropertyChanged(callback: (T) -> Unit) =
object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(observable: Observable?, i: Int) =
callback(observable as T)
}.also { addOnPropertyChangedCallback(it) }.let {
Disposables.fromAction {removeOnPropertyChangedCallback(it)}
}

This way you can store in can be stored together with other Disposables in CompositeDisposable and the class using this does not need to know any details about how to unregister.

The end

And there is a moral of this story. If something is bothering you, try to understand it and/or try to make it better! Good developers always question things!
Thanks to Oleksii Fedorov and his posts about #lifelong #learner who actually made me look into this.

PS: if you want to remove the unchecked cast warning, without suppressing it, Kotlin is there to help, too (check the costs of crossinline though)

inline fun <reified T: Observable> 
T.addOnPropertyChanged(crossinline callback: (T) -> Unit) = ...

--

--

Android @ Soundcloud, Google Developer Expert, Goth, Geek, writing about the daily crazy things in developer life with #Android and #Kotlin