by Jeff Angelini posted on 7/20/2011 2:35:00 PM
Separating the presentation of the application’s UI from the logic of its UI is usually a good idea. This separation of concerns creates more de-coupled code, which is much cleaner, and even allows for more unit testing coverage. Android bundles the UI and UI logic into the Activity class, which necessitates Instrumentation to test the Activity. Since Instrumentation is introduced, it is much more difficult (or impossible) to properly unit test your UI logic when the dependencies in the code cannot be mocked. However, a simple MVP pattern will help de-couple the UI and UI logic in Android applications.
The MVP pattern stands for Model-View-Presenter, and it separates the UI concerns between the data of the UI (Model), the display of the UI (View), and the logic of the UI (Presenter). For Android, the View is the Activity, which will handle gathering user input and updating the display; the Presenter is a class that will handle communication between the Model and View; the Model will handle persisting and retrieving data, along with any business logic that the data must adhere to. Interfaces will be used to de-couple each of these components. A simple Customer View will be used to illustrate how this can be accomplished.
First, our CustomerActivity (the View) will have textboxes for the Customer’s ID, first name, and last name:
private EditText mFirstNameEditText, mLastNameEditText, mIdEditText;
The user will load a customer using the mIdEditText and a Load Button. Likewise, the user will save a customer using a Save Button:
private Button mSaveButton, LoadButton;
We must now create a CustomerPresenter with the following methods:
public CustomerPresenter(ICustomerView View)
public void saveCustomer (String firstName, String lastName)
public void loadCustomer (int id)
We then can wire it all up in the CustomerActivity’s onCreate method:
mCustomerPresenter = new CustomerPresenter(this); mSaveButton.setOnClickListener(this);
mLoadButton.setOnClickListener(this);
The CustomerActivity class must now implement the interfaces OnClickListener (for handling the Button’s OnClickListeners) and ICustomerView (for the CustomerPresenter constructor). The OnClickListener defines the method void onClick(View v), and our method will look like the following:
switch (v.getId()) {
case R.id.saveButton:
mCustomerPresenter.saveCustomer(mFirstNameEditText.getText().toString(),
mLastNameEditText.getText().toString());
break;
case R.id.loadButton:
mCustomerPresenter.loadCustomer(Integer.parseInt(mIdEditText.getText().toString()));
break;
The previous two code sections show that when the Save Button is clicked, the saveCustomer method of our presenter will be called with the Customer’s first name and last name information; and when the Load Button is clicked, the loadCustomer method of our presenter will be called.
We haven’t defined ICustomerView, so we’ll do that now. When loading the customer, the CustomerPresenter will need to be able to update the CustomerActivity’s last name, first name, and ID EditTexts, so ICustomerView will look like the following:
void setLastName (String lastName);
void setFirstName (String firstName);
void setId(int id);
The CustomerActivity’s implementation of these methods will set the corresponding EditText to the value of the parameter.
The CustomerPresenter, then, will look like the following:
private ICustomerView mCustomerView;
private ICustomerModel mCustomerModel;
public CustomerPresenter(ICustomerView view) {
mCustomerView = view;
mCustomerMode = new CustomerModel();
}
@Override
public void saveCustomer(String firstName, String lastName) {
mCustomerModel.setFirstName(firstName);
mCustomerModel.setLastName(lastName);
}
@Override
public void loadCustomer(int id) {
(mCustomerModel.load(id)) {
mCustomerView.setId(mCustomerModel.getId());
mCustomerView.setFirstName(mCustomerModel.getFirstName());
mCustomerView.setLastName(mCustomerModel.getLastName());
}
}
The implementation of the CustomerModel isn’t important for our purposes; we just need to know that it is being saved to a repository of some sort. Furthermore, the CustomerModel is currently tightly-coupled to the CustomerPresenter, which can be remedied by injecting it as a dependency.
The CustomerPresenter allows the CustomerActivity class to be as simple as possible. The activity now only gathers user input for the presenter and provides simple UI update methods for the presenter. Since the CustomerView and CustomerModel implement interfaces and can be injected into the CustomerPresenter, it is not dependent on them. Therefore, they can by mocked, which allows the CustomerPresenter logic to be unit tested.
After Note: This MVP pattern is sometimes referred to as Passive View, since the view only passes along data, either to or from its presenter. The MVP pattern can also be implemented such that the View knows of the model. The view responds to state changes in the model for simple UI updates, while the presenter handles more complex UI logic. This more complex pattern is sometimes referred to as Supervising Controller.
In Android, this can be accomplished by the Model using Java’s Observable class and the View implementing the Observer interface; when something changes in the Model, it can call the Observable’s notifyObservers method. It can also be implemented with Android’s Handler class; when something changes in the Model, it can send a message to a handler that the View injects into it.