Antipattern: UI Databinding Android

This is me going through the Android recommendations on how to do databinding.
As with A LOT of practices that increase the complexity and harm maintainability; databinding and the recommendations enable, perceived, QUICK development, but it has such a high cost afterwards; there WILL be re-writes. It won't be refactorable code.
Using databinding and following these recommendations WILL decrease the maintainability of your code.

What I'm looking at for android is their Data Binding Documentation on 2018/01/01. There are are few high level sections that I'll go through the subsections of.

  • Layouts and binding expressions
  • Work with observable data objects
  • Generated binding classes
  • Binding adapters
  • Bind layout views to Architecture Components
  • Two-way data binding

Even the start where they mention using static methods to enable functionality... No Static Methods! (Apparently I don't have a post on this yet).

Let's jump in!

Layouts and binding expressions

Right off the bat here it talks about something I'm always suspicious of. Not always bad, but always suspect

The Data Binding Library automatically generates the classes

It generates code for you. It does black magic. Generated code that isn't part of your code files is behavior you can't see.

It shows that the way to define it is the full package name for the object. We're not pretty tightly coupled to the code. Our UI is now TIGHTLY coupled to a specific non-UI code object (interface or class). What the hell? A UI SHOULD NOT be coupled to code. This makes the entire system harder to change. At best, an interface should continue to abide by the contract. Downside is that I don't trust code refactoring to update XML files. We need to be free to refactor the files on disk as easily as the code. This coupling of a fully qualified object name with the UI destroys our ability to do that safely.

We're not even out of the introduction about this and there's two things I really dislike.

Data object

Nope. Super nope. This is a databag. I kinda loathe kotlin for putting it in; but it's not an Object of Object Oriented Programming. It's a data-bag that gets passed around. There's no behavior. This shouldn't exist.
It's just a bunch of getters, doesn't do anything, doesn't know anything... This is not object oriented code.

Then it gets into how you can do things in multiple ways. Not just able to do things in multiple ways, it'll be a helper and resolve to multiple things.
If we have a databag, there are THREE ways we can expose the data that the following will resolve to:

 android:text="@{user.firstName}"

We can have it a public field...

public final String firstName;

We can have a getter

public String getFirstName(){return firstName;}

Or we can just call the method firstName

public String firstName(){return firstName;}

Now... I think this is helpful in typical code, because the databags we typically see will exist in all three forms. They've, reasonably, made it work for (I assume) nearly all situations they identified.

But this is shitty. What does it use?
What if I have a class with all three

public final String firstName;
public String getFirstName(){return firstName.ToUppercase();}
public String firstName(){return firstName.ToLowercase();}

What's displayed? I have no idea. Can I specify which of the two methods to use?
Can I specify a non-property?

public String firstNameLowercase(){return firstName.ToLowercase();}

How would I access this? Could I? I honestly don't know right now. I think I could use the resolution and do this

 android:text="@{user.firstNameLowercase}"

But... that's not clear. It's inconsistentcy in the code. The resolution adds ambiguity to what's actually happening, and what's being invoked.

Binding data

The examples here force us to put logic in our activity. In the onCreate method in the examples. We're creating the information to be displayed in the activity. There's probably better ways, but I'm not seeing them as examples.

Are there interfaces available for the generated DataBindingUtil class? Also - Util? ... oh joy, util classes with static methods...
I could ignore a bit of the databinding if we had a clean way to use this and have the UI do nothing. I could make it happen; but it's removing control over the content from the code. The UI then displays everything... Which I guess works for MVC... Oh yea; I'm really not a fan of the MVC and MVVM patterns. Early stages of my Object Oriented exploration had a misunderstanding of the MVVM pattern, creating the idea of the Hotel pattern. I don't do this as explicitly laid out here, but the MicroObjects style comes out A LOT like this.

Expression Language

Oh... the pain...

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

OK, what is this? android:text="@{String.valueOf(index + 1)}" Setting the text to one more than index... It's the text, it's the name... That this is demonstrated in the documentation; where would I, as a dev, look for where the text is set? I'd be looking in the activity class and adapter classes, because this is clearly a list based setting.
This creates multiple places that this value MIGHT be set, and it can change in the future...
Complexity and congitive load increase. This is going to be harder to maintain. A lot of tribal knowledge is going to accumulate and be hard for onboarding.

android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" What is 13? Why 13? What does that value signify? What if that business logic changes? How many places in the code AND THE UI... holy fuck, and the UI... That's disgusting.... How many places in the code and the UI will need to change? Are all instances of 13 for the same business logic?
Putting it into the UI removes any ability to define the business rules around it.
I wonder if it'll let you call methods that return booleans, like the above firstNameLowercase question... Regardless, you've coupled business logic in your UI. Remember what we REALLY REALLY wanted to get away from - Buisness logic in the fucking UI.

Don't put business logic in the UI

IT PUTS BUSINESS LOGIC IN THE UI!

That should be all the only reason needed to kill the idea of using this 'Expression Language'.

Don't put business logic in the UI, it's never worked out.

Resources

... I'm actually not oppposed to this; as a basic idea. Resources are UI components. It makes some sense to be able to do a little dynamic-ness around what the UI looks like.
I'm OK with this in a limited sense. I expect it could be hella abused, but used carefully; yeah.

Event handling

I don't like the examples they have. It hurts me.
I don't like how Android does event mapping. I think event handling set up is kinda broken. There should be some cleaner ways to do it. That there are things like Dagger and this data-binding library coming around to simplify how to bind methods to events shows there's a clear failure on the OS/API side to make the developers life easier.

That being said - How the event handling works in this library is so easy to abuse.
We're often looking for ways to pass data around across events. We'll use 'Tag' or stuff it somewhere to be able to associate it with the event.
The 'Listener bindings' style allows massive abuse of this. You can 'bind' anything to the ui then have a custom defined handler invoked. I like where they are trying to go, I don't like how they have to get there.

I prefer custom controls.

The MyHandlers example is going to end up a catch-all class with various ways to decide the appropriate path to call into. It'll end up with many reasons to change, lacking the Single Responsibility it should have.

I do like that they call out the risk

listeners containing complex expressions make your layouts hard to read and maintain

but it shows that this can easily lead to complexity and lack of maintainability.

Imports, variables, and includes

I understand the reason for most of these, I think it's a symptom that this isn't the way to go. That when you start to increase the complexity of your UI with code objects, it's amplifies the complexity and cognitive load required to pull in more.

Imports are like java imports, you can refernce things by a shortname, not the FQD. But then... why are you making this level of functionality show up in the UI?
Yes, even just visibility

android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

Why is this determined by and in the UI? It's untestable.

The imports leading to the need for alias' just hurts.

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>

It's absolutely needed because of the increasing complexity... but it shouldn't.

I don't like type aliases like this. I don't like them in general for single code files. It's giving a known thing a different name. It increases complexity, cognitive load and debugging difficulty.

The ability to pass a binding to an included layout, makes sense from the idea of doing the binding, but it's ugly.
It means that the architecture of your code is insufficent to fascilitate getting the data where it needs to be. Instead you hack a path through the UI. It's a bad practice that's going to become painful in some form as the app changes.

Work with observable data objects

I like the idea of detecting a change and updating a value; but do it w/o violating encapsulation.
Having public fields is not the way to do it.
I haven't had much need to actually use observables in the android work I've done, so I don't have a lot to say here that doesn't mimic what's been said in the data objects section.

Generated binding classes

As far as I can tell (and I haven't used this)... It's generating code to actually do the databinding. This makes it so you ahve to do things differently than Android normally does it.

That makes sense; using a databinding framework forces you to use the databinding framework to bind the data.

This almost makes me wish to deal with Microsoft's data binding... shudder

This section makes it clear that this does things that may not be desired and FORCES things that may not be needed. The "Views with IDs" section.

Binding adapters

Reading through this binding adapters section makes my skin crawl a little.
The automatic method selection essentially turns the code-behind into dynamicly typed code.

It will look for the right method. No behavior contract or interface required, just whatever you tell it; whatever you bind it to; it'll look for the right method three ways (as mentioned in the above Data Objects section). This is ... sloppy. I've written about developer discipline and this has the same effect; it enables bad code. It enables bad practices. My entire premise against databinding is that it enables bad practices and here we are able to define and bind ANYTHING and use ANY method on it to populate data. The Property we're binding to is even suspect.

For example, given the android:text="@{user.name}" expression, the library looks for a setText(arg) method that accepts the type returned by user.getName()

This means we can define any ol' method on our custom control and have any ol' thing set. While this can be useful and effective to work with shoddy code - it is sloppy and lazy. There's no longer constraints shaping the code. It's free form, it's undefined, it's able to do whatever in a fashion intermixing code with ui. It enables quick hacks to demonstrate... but we prototype into production so often; this perceived speed from databinding bites us in the ass quickly; ussually before we even ship the MVP.
I saw it on two products; bit them both. Product I lead - no data binding, no ass bitten.

Not only can you define your own setters and getters, but you can REDEFINE UI attributes to use a different method... Oh yea; no confusion going to happen there.
"For this control, android:text sets the |not text| - WTF"... It'll make sense in the moment to hack something in, I'm sure.

These "features" ONLY increase code complexity. They are detrimental to the products sustainability.

Redefine, add logic... A bunch of public static methods floating around can SECRETLY change how the UI operates. I can't see how it's clear that these things are used or not on a UI - But holy hell they are going to make debugging a pain-in-the-ASS when something isn't going right.

I'm skimming most of this section now... but I can't see any positives.

Oh, and they finish off with logic in the UI... fitting.

Two-way data binding

I'm seeing a lot of non-code causing behind the scenes behavior. I'm not a fan of my product running a lot of code to do the things I want without me seeing or controlling them.
Java is very boiler-plate-y, these get rid of a lot of that; but do so by removing the code from it. I don't like that.

Converters - Yep; do some conversion logic in the UI file. Perfect. That's exactly where it belongs, in the untestable text file.

Summary

The Android recommendations on their databinding are going to make the app less maintainable. The complexity and cognitive load that will be required to work on the app will be beyond what we can handle.

There will be more bugs.

DataBinding allows sloppy development without requiring discipline. What you can get done with databinding makes the entire product far less maintainable; and will make the lives of the engineers involved much harder.

Show Comments