I wanted to play with the DataBinding library ever since Google introduced it back in 2015. I didn’t seem to find a good opportunity to try it though. The tutorials and introductions seemed interesting but rather trivial.
So, a couple of months ago I decided to just dive in. I picked up the WordPress Android app, on which I work daily anyway and just use DataBinding, somewhere. Being far from trivial, the WP app would serve as a good base to try the library on.
The code lives at hypest/mvp-vm-databinding. Read my analysis after the break 🙂
I chose to port the Posts list. That’s the list of published and draft posts the user maintains on a WP site/blog. Design wise it’s a list of “cards” with an image, some text and a few buttons each. There are also a few actions affecting the list as a whole (e.g. a FAB to create a new post).
My plan was to refactor the UI code to use DataBinding and see where that takes me. Looking around the web, it didn’t take long to notice that many are associating the library with the MVVM pattern (Model-View-ViewModel). That was a nice excuse to play with alternative code-organization patterns. After all, Android’s unscalable Model-View pattern makes me sad; it mixes UI and business logic and usually leading to “God” objects (huge Activities/Fragments ending up doing everything).
Stab at MVVM
And so, started moving code out of the Activity/Fragment/Adapter and into ViewModels in the form of getters. The DataBinding lib would call the getters found in the layout. It was interesting: I no longer needed to know exactly when to put something on the View. I just needed to provide a function to return the value the View needed (for example, the integer to set the View’s visibility).
But got disappointed after a short while: I ended up with “God” ViewModel classes that had lots of business logic while not being clear what was used by DataBinding and what not. It looked messy so I decided to continue looking into UI architectures in hope of finding something I liked more.
Enter Model-View-Presenter, aka MVP. Another buzzword often found in Android posts and talks so I had to give it a shot. The main premise of the pattern (keep in mind: a pattern, not a framework) is to separate the concerns of holding (or downloading) the data (Model), managing the view itself (View) and controlling how to get the Model data and prepare them for display (Presenter). I took great inspiration from the nice Google samples for android architecture repo.
Being in my DataBinding experiment, I still wanted to have the DataBinding lib handle the last mile to the View so, the presenters would need to play along: no direct access to the view, so forget code like
view.setVisibility(). Basically, no references to Views. A couple of iterations later and by using simple ViewModel classes, I got something I liked.
DataBinding flavoured MVP
The DataBinding library still gets its data from a ViewModel class. This time, the ViewModel is just a bunch of simple variables (I kinda lie: they are Observables of simple variables) and that’s it. No logic implemented apart from some animations related code. Any logic behind the view lives in the Presenters.
Speaking of logic, even though the DataBinding lib permits and has extensive support for putting logic directly inside the layout file, I stayed away on purpose. I find it an awful idea to have the layout harbour actual code.
With that out of the way, here’s a list of the players and their role in the game:
ViewModelclass that holds a set of observables that DB maps to the UI
ActionHandlerinterface to link any UI actions to callbacks (think: onClick event listeners)
Presenterclass that triggers the loading of data and updates the variables in the ViewModel after receiving the data. The Presenter also implements the ActionHandler interface to respond to user actions.
Viewinterface (poor naming, not to be confused with Android Views) to abstract away the callbacks needed to be performed by a Fragment or an Activity (like, start another Activity)
The cool thing here is that when the Presenter sets the values in the ViewModel, the DB gets notified and updates the corresponding UI. Boom! The Presenter is free from checking whether the View is there or not. All it does is to set a few variables in the ViewModel. Nothing “dangerous” with setting POJO fields!
I could hear you ask now: where’s the Model? Well, the WordPress app has an extensive layer for fetching and storing the data already so, no work needed at that level. Though, after implementing the Presenters, I was left with the “thirst” to clean up the way the Presenter was asking for the data. Thankfully, we’re already under way of cleaning that part as well with the WordPress-Stores project.
Even though it can be done with a single set of View+ViewModel+Presenter for the whole Posts list, I opted for splitting the responsibilities of the list from the individual list items. So, there are 2 sets of View/ViewModel/Presenter classes:
- One for the list “view” with its FAB, the list container, the empty-list views, etc and
- Another one for the post “card” UI with its featured-image, post title, post excerpt and post action buttons
Here’s a diagram of how the post “card” classes are structured and how the data/control flows:
There is an important thing to note in this diagram: Neither the Presenter nor the ViewModel reference any Android Views and do not set any properties on them directly. It’s the DB lib that when bound, listens for changes in the ViewModel variables (Observables) and reflects them to the UI. That means, no need to constantly check whether the Fragment is added to the Activity as we need to do in the typical way Android apps are implemented with fragments!
Nothing’s perfect of course. An exception to the “not referencing Android views directly” rule is made for the BindingAdapters. Those are static functions tasked with performing some setup or action on an Android View. They are called by the DataBinding lib when their data (parameters) change. For example, I’m using one to set the RecyclerView’s adapter, or to animate the Post card buttons.
Being static makes the BindingAdapters harder to integrate with the rest of the code though. The only data they can work with is what gets passed as a parameter, leaving no place for keeping some state other than those parameters themselves. That means either keeping state in the View itself (like, checking whether the RecyclerView has an adapter already) or in the other parameters. To me, that looks messy and hacky.
On the same vein, triggering animations also tends to be awkward. Check out the way to trigger the animation of a View: onAnimateButtonRow(). Because the BindingAdapter can be called whenever any of its parameters change, the animation can start involuntarily and so, I’ve added a dedicated variable (
AnimationTrigger) just for controlling the triggering. That variable pollutes the layout though 😦
RecyclerView and Adapter
The typical setup of a list in Android is to use a list component and a list adapter. In this case, it’s a
RecyclerView and a
RecyclerView.Adapter. What’s also typical is to have the Adapter hold the list of data and reflect them on the Views when its
onBindViewHolder() is called. With the DataBinding lib picking up the role of reflecting to the UI, the adapter is minimized into just providing the data to bind to the lib. It took me a few iterations before I finalized exactly which data structure to use for holding that data and I eventually opted for a list of PostPresenter instances.
Probably not the cleverest of options since it increases the coupling between the Adapter and the PostPresenter but, it looks “cleaner” and easier to understand. The Adapter ends up being just a “plumber”, connecting a couple of endpoints between the PostPresenter and the data-bound layout.
List change events
The posts list is an “infinite” list: you scroll down and older posts are loaded or fetched from the server. I wanted to enable the recommended change events of the RecyclerView and so I made the Adapter’s PostPresenters list an ObservableList and used its
addOnListChangedCallback() to trigger the proper
notify*() method. So, when posts are added/removed/etc the default system list animations are triggered. Good 🙂
One of the promises of MVP in general is to augment the testability of your code. I did add a couple of Presenter unit tests to get the feel. That’s far from enough coverage of course, but it served its purpose for the experiment. Testing the presenter is straightforward: Start it up, trigger one or more of the its ActionHandler methods and the check the values in the ViewModel. The assumption here is that we only test the Presenter (and its ViewModel) because the DataBinding lib is trusted to put the values correctly on the View itself on the real app.
I had a goal of using Robolectric to speed up the testing but I wasn’t successful at making it work with the project. I guess more effort is needed on that front.
The experiment left quite a few questions unanswered and would like to expand on at some point. For example, I didn’t draw a final conclusion on what exactly do the Presenters need to implement in terms of lifecycle support.
I wanted to play some more with trying to persist the Presenter on configuration changes (read: device rotation) by binding and unbinding them dynamically and see what exactly that ends up to.
Some form of Dependency Injection would also be nice, at least to get the
Context out of the Presenters.
It will need an extra run of the experiment to get those answered 🙂
This was a good experiment. Gave me the opportunity to put my hands on the DataBinding library plus a first stab at MVP, which I was eager to try as well. Here’s a rundown of the good and the bad, the way I saw it.
- Nice separation of concerns:
- DataBinding reads data from the ViewModel and executes callbacks in the ActionHandler
- Presenter asks for data, sets up the ViewModel and responds to user actions. No direct access to the Android Views (huge positive).
- Fragment/Activity only enters the picture when high level Android actions need to be performed (like starting another Activity) and some basic life-cycle events need to be handled
- Easier to reason about as the “players” have smaller scope
- Very small Activity/Fragment classes
- Less boilerplate (goodbye
- Proper RecyclerView animations by using add/remove/change events
- More classes to keep in mind
- Not really straightforward to port existing code. You can limit yourself to just eliminate
findViewById()which is still a win and easy to do.
- Animations are hard to bind (e.g. animate some views on a button click) and not “clean” to trigger
- BindingAdapters can be hard to implement and even harder to integrate (since they’re static functions)
- Presenters are not really Android framework free (you need the Context to load resources the least) so you cannot unit test on the JVM which would be faster. You need the Android testing frameworks. Not a deal-breaker, but yeah, the speed gain would be nice to have.
Will I use MVP or DataBinding again? Oh, for sure!
A big thank you to @0nko and @spirosoik for reviewing this post!