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.
Aidan Mcwilliams says
I agree that android dependencies in a view model can make it harder to test, but for your example, View.VISIBLE, View.INVISIBLE and View.GONE are also only integer constants (like resource identifiers).
I usually just expose these in my view models, that way there’s no confusion whether false means GONE or INVISIBLE.
But then the view model’s property would need to change, you would then need something like “commentsVisibility”, which can just be a dependent property of “hasComments”, then when “hasComnents” changes, the dependent “commentsVisibility” property will be queried by the binding.
Donn Felker says
True, this is one way that you can do it. In fact, I’m doing some quite intricate databinding in my current project that is very similar. I could have explained it that way in this post, but I wanted to keep it simple.
I feel that using Int’s in your ViewModel is Ok. Such as resource ID’s and such. those I’m cool with too as they’re easy to work with in unit tests as well.
jacquesgiraudel says
You have the code in the ViewModel but you do not know where it is used.
The ViewModel loses this logic coordination.