One of the things that’s great about MVVM (I’m using data binding in my implementation) is the lack of the boilerplate that you have to deal with. When working with the MVP pattern you are forced to deal with an obscene amount of get/set boilerplate code. Sure, this does make your UI logic more testable but it doesn’t get around the fact that there is a lot of boilerplate.
While MVVM with Data Binding does remove a good deal of this boilerplate you also run into new issues where logic is now present in the views, like this:
<TextView ... android:visibility="@{post.hasComments ? View.Visible : View.Gone}" />
As Joe Birch accurately pointed out in his article – this has a code smell and it just feels gross.
The logic is now buried in an Android XML view and its near impossible to test unless you’re rigorous about your Espresso tests … and let’s be brutally honest here… you’re not rigorous about your testing.
Removing Logic from XML Views with Custom Binding Adapters
Removing logic from XML Views is quite easy with custom BindingAdapters. Early adopter and DataBinding aficionado, Lisa Wray, posted about this back in 2015: Pro tip: More data binding — Easy view visibility in XML! I heard you guys l….
In short, you create a binding adapter in Java (or Kotlin as I’ve done below) and drop it into your project.
@BindingAdapter(“isVisible”) fun setIsVisible(view: View, isVisible: Boolean) { if (isVislble) { view.visibility = View.VISIBLE } else { view.visibility = View.GONE } }
The logic for showing a view is now determined by a Boolean value. To use this in an MVVM Data Binding XML View you’d do the following in your view:
<TextView ... app:isVisible="@{post.hasComments()}" />
The logic for the hasComments code is now kept inside of the View Model which can be easily unit tested.
Testing the Custom BindingAdapter
We may have removed the logic from the XML view, but we still have code that needs to get tested. Now that the view logic is based upon a Boolean we can easily test this with an Espresso test:
@Test fun isVisibleShouldBeEasilyControlledWithABoolean() { val v = View(InstrumentationRegistry.getTargetContext()) setIsVisible(v, true) // visible assertThat(v.visibility).isEqualTo(View.VISIBLE) setIsVisible(v, false) // gone assertThat(v.visibility).isEqualTo(View.GONE) }
You’re going to put this in your androidTest folder.
I know what you’re thinking – this is an Espresso Test, it runs slow. Not really. You’d be surprised at how fast this test runs as it does not need to fire up an activity and start clicking on buttons/etc.
You now have logic that can (and is) tested via a simple test. Your display logic is then kept inside of your View Model (does a post have comments or not, that’s a fairly simple true/false boolean).
Furthermore … your ViewModel is not riddled with Android package references (which can make it harder to test). Which brings me to …
Keep the ViewModel free of Android Dependencies
One goal that I have is to keep the View Model free from Android Dependencies if at all possible. This allows me to utilize JUnit unit tests for a quick feedback loop. I can write, test and iterate much faster with a unit test than I can with an Espresso based test.
Yes, you could put some of this logic into the View Models, but I find keeping it as clean a possible provides for the best possible outcome when it comes to testing.
As with everything, there are always caveats to this – using resource identifiers (as they’re only integers, etc). My rule of thumb is to try to avoid the Android packages in my view models. That way it makes testing a snap.