At a high level, Android’s Data Binding library is a great concept. Update the data in your view model and the corresponding view is updated automatically. In this article, I will describe why I feel that this library encourages writing bad code which must be carefully managed, as well as some more general drawbacks. This is based on problems that I experienced on an actual project using this library.

Separation of Concerns

My primary problem with data binding is that it encourages introducing presentation logic into your xml files. It should go without saying, but layout files should exclusively define your screen’s layout and not contain any logic. An innocently simple example of this is code like:

android:visibility=”@{viewModel.showTitle ? View.VISIBLE : View.GONE}”

To update the visibility of a view, you have to choose between using a boolean and implementing a ternary in the XML, or setting the appropriate integer in your viewmodel, which requires importing Android classes (I’ll touch more on why this is a problem later on).

You could be forgiven for allowing the above code into your app, given how straightforward it is. However, once you open the door to including logic in your layout files, it can become a slippery slope. As an example, later on in the project the following code appeared:

android:visibility=”@{!viewModel.hideFragmentLayout && !viewModel.showError && viewModel.vehiclesViewModel.showFullScreenProgress ? View.VISIBLE : View.GONE}” android:visibility=”@{!viewModel.isError && !viewModel.hideFragmentLayout ? View.VISIBLE : View.GONE}”

This is one of the more extreme examples of more complex visibility logic. The alternative to this is introducing a variable for each combination of conditions and updating them appropriately.

This becomes even more apparent when you include layouts inside your databound layout. To do this, you create a variable or variables inside your sub layout, and then your parent layout binds to those variables. Since thats a pretty poor explanation, lets look at an example:

<include

layout=”@layout/layout_full_screen_error”

app:errorMessage=”@{viewModel.errorMessage}”

app:errorTitle=”@{viewModel.errorTitle}”

app:imageResource=”@{viewModel.errorImage}”

app:onClick=”@{() -> viewModel.onErrorRetryClicked()}”

app:visibility=”@{viewModel.showError ? View.VISIBLE : View.GONE}” />

And the included view:

<data>

<import type=”android.graphics.drawable.Drawable”/>

<import type=”android.view.View”/>

<import type=”android.text.TextUtils” />

<variable

name=”visibility”

type=”java.lang.Integer”/>

<variable

name=”errorTitle”

type=”java.lang.CharSequence”/>

<variable

name=”errorMessage”

type=”java.lang.CharSequence” />

<variable

name=”onClick”

type=”android.view.View.OnClickListener”/>

<variable

name=”imageResource”

type=”Drawable”/>

</data>

The general point of this example that including layouts is messy. I’m not even going to bother pointing out the onClick binding, as I think I’ve illustrated my opinion on that fairly well by now.

It’s also worth noting that each of the above variables need to be accessed in the following way:

android:visibility=”@{visibility != null ? visibility : View.GONE}”

This is due to a bug in the data binding library where it doesn’t rebind the variables in a child layout when resuming a screen (this was on an older version of the library, they may have fixed this by now).

Speaking of slippery slopes, whilst grabbing the above example I found this line of code in the child layout:

android:visibility=”@{TextUtils.isEmpty(errorMessage) ? View.GONE : View.VISIBLE}”

Another example of how view logic creeps in.

Inconsistent or Unclear Usage

Most of the examples so far have focused on the introduction of view logic into the xml, but at a higher level there is no clear definition of how you’re even meant to bind the data. In one ViewModel we had three different ways of updating that data

If your view model extends BaseObservable, then you can annotate a variable with @Bindable

You can have an ObservableField

val errorMessage = ObservableField<CharSequence>()

Or you can have an ObservableCommand which the view (Activity/Fragment) can then listen to eg

val showLoading = ObservableCommand<Boolean>()

with the corresponding listener

viewModel.showLoading.subscribe {

progressBar?.showLoadingAnimation(it)

}

We had some general rules around which method should be used in what context, however because of the broad nature of development and the relative similarity of each methods, there was still a lot of ambiguity as to which method should be used and where, making it more difficult to develop within this framework as well as reading/debugging code.

EDIT: Since originally writing this article, Google introduced the Architecture Components library, introducing another method of binding data via the LiveData class which can be used similar to ObservableField or ObservableCommand. It appears that this is meant to be the de-facto method of binding.

ViewModel as a POJO

I touched on this before, but it is preferable for ViewModels to be plain old Java objects. This is partially for semantic reasons, but it also assists in unit testing. However, because the ViewModel is responsible for setting text/drawables/etc, to achieve this we ended up wrapping parts of the android SDK. This led to code such as

uiService.hideKeyboard()

Which is a method on a UiService interface that is implemented as either an ActivityUiService or FragmentUiService an injected into the view model using dagger. This should be obvious, but a view model should not be responsible for showing and hiding the keyboard.

Even worse, since the view model sets text/images etc and we want the advantages of localisation and scaling (respectively), we had to introduce a ResourcesService to wrap Android’s resources in a POJO friendly way, for example



fun getText(

…

} interface ResourcesService {fun getText( @StringRes resId: Int): CharSequence

It’s important to note that even though we’ve wrapped the resources class we still need to use the generated resources class to look up the values, so we haven’t actually achieved our goal of excluding Android classes.

Another example is our NavigationService:

interface NavigationService {

fun finish()

fun finishWithResult(resultCode: Int)

fun finishWithResult(resultCode: Int, returnIntent: Intent)

}

Which is again not something the ViewModel should be responsible for and yet again requires Android classes (specifically Intent).

Your Architecture Should Prevent You From Writing Bad Code

When discussing my issues with data binding with a colleague who is quite fond of the library, his response was that a lot of my problems could be avoided by carefully managing the codebase. Whilst he is correct, a good architecture should limit your ability to write bad code, not encourage it, because eventually something will slip between the cracks, setting a precedent and starting you down the slippery slope.

Testing

I touched on this before, but it’s worth mentioning again: with databinding, your presentation layer becomes very difficult, if not impossible, to unit test.

The introduction of Android classes in your view model means that to perform unit testing you need to include the applications generated R file as well as mocking several Android classes, some of which could be final.

Worse yet, if you have included presentation logic on your layout files or Activities and Fragments, which given the design of the library is quite likely, then you’re pretty much out of luck when it comes to testing. If someone knows how to unit test an xml file, please let me know.

Compilation

With the combination of databinding, kotlin and dagger all doing compile time code generation, build times on the project were a nightmare. Furthermore, because of multiple libraries generating code at compile time, incremental kotlin compilation and instant run would both fail on a regular basis and had to be disabled. Anecdotally, another project I’ve worked on which only used kotlin and dagger but not databinding did not suffer from this issue.

TL;DR

I asked a couple colleagues who had used databinding in their project about their thoughts on it and general feedback was “I had no idea where to find logic”, which is why I dedicated quite a lot of this article to specific examples of how logic is spread across multiple areas with no clear indication of which area it should be in. Given that a large portion of development is reading code, it is important for code to be easily discoverable. Add to that poor testability and long compilation times and it should hopefully be easy to understand why new developers had difficulty ramping up on the project and why I decided not to use it in subsequent projects.