An Untold Story of Observable Dependencies in Data Binding
Hope you all heard about data binding. If not, head out here and check it out.
So one usage of Data Binding is replacing findViewById and another one is, binding data obviously.
The true power of data binding comes when we use it with Observables (ObservableInt
, ObservableBoolean
, ObservableField
etc..). Instead of binding data using regular fields/methods, if we binding it using Observable types, UI will be automatically updated whenever we change the Observable values using set()
calls.
But what if one Observable depends on another Observable to get its value. Let’s take an example of updating text of an TextView
using an ObservableField<String>
and updating it’s visibility based on whether it have any text or not.
To update visibility, we would have another field of type ObservableInt
(which can hold View.VISIBLE or View.INVISIBLE or View.GONE).
Now how would we update the textVisibility
? Whenever we update text
? That would be quite a boilerplate and not exactly foolproof because if we have multiple Observables depends on multiple other Observables, even if we forgot to update one Observable in a place, there would be an issue. We can do better.
Let’s step back a little bit and think about what we actually want to solve. textVisibility
should be updated whenever text
value is updated. That’s all.
Observables can depend on other Observables and can react to changes from the dependents.
This change was done with Android Studio 3.1 release last year.
The
ObservableField
class can now accept otherObservable
objects in its constructor.
With this, we can pass other Observables as dependencies when creating Observables which can react to changes when the dependent Observable change. So, instead of this,
we can do this.
You might think the code actually got bigger due to the ObservableInt
anonymous class creation, but those can be abstracted away into an until function. The thing is, now we are not updating the textVisibility
manually.
Data Binding Extension: map
To abstract the object creation part away and to make the dependency thingy more functional, I wrote an extension function for that.
Does the method name look familiar? You would have seen this name often if you used RxJava or Kotlin collection utils. Since we are transforming one Observable field to another, the name suits here perfectly.
Now the TextViewModel.kt would be like this
You might notice that the textVisibility
type is now changed from ObservableInt
to ObservableField<Int>
. This is because of the return type from map
function and we made that way because we need to map
function to work will all Observables. Of course, you can make another function that does the same and return ObservableInt
if you want.
Let’s take an another case where we do some form validation and submit button should be visible only if all fields are filled.
This is just an example, but in real project, we won’t have these update* methods and those fields would get the latest value directly from the EditText through two-way binding. In that case it gets more tricker since we have to add PropertyChangedCallback to each Observable fields to know when update happens and we should call the checkAndUpdateSubmitButtonVisibility
method there. I’m not gonna show the code for that, because, as you can image, the code would be quite a lot.
Now we can simplify and solve the boilerplate issue with our Observable dependency concept.
Let’s write an util extension function for this too.
Data Binding Extension: combineLatest
Again, this function name is inspired from RxJava combineLatest operator. Now the ViewModel becomes,
Note that, the combineLatest
method we created here only works with this example where one Observable depends on three other Observables because, we get all the Observables as individual parameters instead of vararg
since we can’t deduce the type of each Observable from vararg
and each Observable can have different type.