Android MVP-B (Model-View-Presenter-Binder)

Lately, I have been using my own derivative of the Model-View-Presenter pattern on my Android projects. The pattern I use is a combination of the Passive View pattern and the Model-View-ViewModel pattern, but with the added benefit of using Android Data Binding.

Android Fundamentals: Data Binding

A soup-to-nuts exploration of the Android Data Binding library.

Both the Passive View and Model-View-ViewModel patterns have their benefits, but both have drawbacks when implemented on Android. Let’s look at the pros and cons of the Passive View pattern first.

TL;DR - show me Android MVP-B

The Pros and Cons of the Passive View Pattern

The Passive View pattern provides a way to split your view’s UI components from the code that updates those components (often resulting in a class called the presenter). This is especially important if you want to be able to test that presentation logic without dealing with those UI components. This separation is beneficial for Android development, where testing UI components can be difficult and slow.

In the Passive View pattern, the view reacts to a change in its state - a user gesture or input event. The view delegates the handling of that event to a presenter. The presenter then processes that event and updates the view. In order to isolate the presenter from the view’s dependencies, we extract an interface that models the view updates. We may even go so far as to extract an interface from the presenter for use by the view.

Each time we need to add new functionality to the view, we need to add an update method to the view interface and provide the implementation in the view. Then we need to add the implementation logic to the presenter. If we are responding to a view event, we need to add a method to the presenter interface and provide an implementation.

When we implement the view, we need to be cognizant of any alternate layouts. An alternate layout may have a different implementation of an update. For example, a tablet view may have an element that has been left out of a phone layout. The view class must handle this difference.

Summary: The Passive View Pattern on Android

Pros
  • The presenter can be tested in isolation from the view (using JUnit).
  • The view can be tested separately from the presenter (using Robolectric).
Cons
  • Adding additional functionality requires updating both at least one interface and both concrete classes of the presenter and view, along with updating the layout.
  • The presenter is still tied to the view implementation.
  • The view still needs to deal with updating alternate layouts.

The Fallacy of the Model-View-ViewModel Pattern on Android

I mentioned that I am using the Data Binding library from Google. The “obvious” pattern to use here is the MVVM pattern. But, pure MVVM is not possible using Android Data Binding. MVVM was developed for use with XAML (eXtensible Application Markup Language). XAML has a markup component and a code component that are bound together. This made MVVM possible and, perhaps, necessary. Android Data Binding has no such concrete tie between markup and class. Instead, the connection between View class and layout is provided by code generation of a binding class. Even though its not a native connection, the Android Data Binding provides for a very interesting and powerful pattern.

Model-View-Presenter-Binder By Example

With Android Data Binding, I have derived code using a pattern that is part MVP and part MVB (Model-View-Binder - or MVVM not tied to XAML). So, I offer up a new acronym: MVP-B. The full power of MVP-B comes from using a presenter with a data-bound layout that is backed by a custom view.

The layout uses the custom view for its root node. Here is the layout file (including the data binding variables). The custom view is derived from a RelativeLayout. Notice that I only have two variables - one for the view model and one for the event listener.

<!-- main_view.xml -->

<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable name="viewModel" type="com.codeprogression.mvpb.MainView.ViewModel"/>
<variable name="listener" type="com.codeprogression.mvpb.MainView"/>
</data>
<com.codeprogression.mvpb.MainView
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<!-- The numberText will updated when the view model's number field is changed -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:numberText="@{viewModel.number}" />

<!-- The button will send click events to the listener (the MainView class) -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:onClickListener="@{listener.add}"
android:text="+" />
</com.codeprogression.mvpb.MainView>
</layout>

The custom view creates an Observable view model and applies the view model to the layout binding. It also sets itself as the listener for view events. It then attaches the view model to a presenter. The binding will react to any changes in the view model, based on the expression defined in the layout.

// MainView.java

public static class ViewModel extends BaseObservable {
public final ObservableInt number = new ObservableInt();
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (isInEditMode()) return;

// Create the view model
if (viewModel == null){
viewModel = new ViewModel();
}

// Bind the view to the layout
binding = DataBindingUtil.bind(this);

// Set the binding variables
binding.setViewModel(viewModel);
binding.setListener(this);

// Attach the presenter to the view model
presenter.attach(viewModel);
}

When the custom view receives events bubbled up by layout’s binding class, it delegates the event to the presenter.

// MainView.java

public void add(View v) {
// Delegate the action to the presenter
presenter.add();
}

The presenter acts on the event and updates the view model.

// MainPresenter.java

public void add(){
// Perform the non-view work
// If needed, offload work to the domain/model layer
int number = getViewModel().number.get() + 1;

// Update the view model
getViewModel().number.set(number);
}

The layout binding listens for changes in the view model and updates the view.

The presenter has no dependency on the view - so no view interface is needed. Additionally, by using a custom view and presenter to encapsulate the view behavior, the Activity is only responsible for inflating the view and handling lifecycle events. It has no responsibility for the view interactions.

So, why not make the presenter the target of the events? Why are we delegating from the custom view?

We include this additional layer in order to ensure the presenter is not tied to the Android framework. The presenter can be tested as a POJO using JUnit without an interface for the view. We are able to act directly on the view model observable and verify the changes made to the view model by the presenter. Also, by moving all of the view interaction from the Activity, we are not dependent on the Activity lifecycle either.

// MainPresenterTest.xml

@Test
public void test_presenterUpdatesViewModel(){

// Note: The ViewModel's number property is initialized to 10
presenter.attach(viewModel);
presenter.add();
assertThat(viewModel.number.get()).isEqualTo(11);
}

The code for this is available on GitHub at codeprogression/android-mvpb.


There are a many interesting posts regarding the MVP architecture on Android. Here are a few to check out:

Also, be sure to check out Episode11 of Fragmented Podcast – The Fowler Road to a Clean Architecture for a discussion of the Passive View and other presentation patterns on Android.

comments powered by Disqus