“Model-View-Presenter: Our Choice of Architecture for Your Android App”

MVP (Model-View-Presenter) is one of the most popular architectural patterns used for application development. It was created to make module testing easier and separate business logics from the overall presentation.

MVP

MVP makes the project’s architecture modular, meaning that it’s very flexible. This, in turn, allows adding new features and ideas faster and at much lower cost, altogether consequencing into better quality products.

An architectural solution built on MVP presents borders between the product’s presentation logics and its display of data. Of course, if the pattern is used incorrectly, those borders are broken, completely wasting the whole idea of MVP.

Let’s look at the MVP’s separate components:

  • Model is used to work with data. The data could come either from a remote server or a database.

  • View implements data display. All of the UI-related work happens here.

  • Presenter implements the interaction between data (Model) and presentation (View).

What is truly great about MVP is that it is in fact very forgiving, and can be implemented in a variety of ways without having its principles. In this article we’ll focus solely on a specific example of implementing MVP in Android development.

Let’s assume that our screen us is a list of items (in our particular example it’s a list of notifications). In this implementation, we’re using a Contract interface, which in turn consists of two separate interfaces - View and Actions. Our View class directly implements the View interface, which performs UI-related work. In order to process all of the necessary View events (such as button clicking, etc.), the Presenter class implements the Actions interface.

public interface NotificationsContract {

   interface View {

       void progressDialog(boolean show);

       void showError(String message);

       void listItemClickAction(NotificationsAdapter.OnItemClickCallback onItemClickCallback);

       void loadMoreNotificationsListAction(NotificationsAdapter.OnLoadMoreListener onLoadMoreListener);

       void setNotificationsContent(List<Notification> dataList);

       NotificationsAdapter getNotificationsAdapter();

   }

   interface Actions {

       void clickNotificationsListItem(Integer position);

       void loadMoreNotificationsList();

   }

}

In the code snippet below, you can see that the first thing we need to do in our fragment or activity is create and initialize View and Presenter.

public class FragmentNotifications extends BaseFragment {

   private BaseActivity activity;
   private EventBus eventBus;
   private NotificationsView notificationsView;
   private NotificationsPresenter notificationsPresenter;


@Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       super.onCreateView(inflater, container, savedInstanceState);
       View view = inflater.inflate(R.layout.fragment_notifications, container, false);
       activity = (BaseActivity) getActivity();
       eventBus = activity.getEventBus();
       notificationsView = new NotificationsView(activity, view, eventBus);
       notificationsPresenter = new NotificationsPresenter(activity, notificationsView, eventBus);
       eventBus.register(notificationsPresenter);
       return view;
   }

}

Below is the example of how your Presenter might look. It is implemented by Contract.Actions. We receive all server data using Model and transfer it to View.

public class NotificationsPresenter implements NotificationsContract.Actions {

   private BaseActivity activity;
   private NotificationsContract.View view;
   private EventBus eventBus;
   private CommonMode commonModel;

   public NotificationsPresenter(BaseActivity activity, NotificationsContract.View view, EventBus eventBus) {
       this.activity = activity;
       this.view = view;
       this.eventBus = eventBus;
       init();
       setupActions();
   }

   private void init() {
       commonModel = App.getInstance().getModelService().getCommonModel();
       setupList();
   }

   public void setupList() {
     //commonModel.geNotifications()...
     //view.setNotificationsContent(response.getContent());
   }

   private void setupActions() {
       view.listItemClickAction(new NotificationsAdapter.OnItemClickCallback() {
           @Override
           public void listItemClickCallback(int position) {
               clickNotificationsListItem(position);
           }
       });
       view.loadMoreNotificationsListAction(new NotificationsAdapter.OnLoadMoreListener() {
           @Override
           public void onLoadMore() {
               loadMoreNotificationsList();
           }
       });
   }

   @Override
   public void clickNotificationsListItem(Integer position) {
       //do some stuff
   }

   @Override
   public void loadMoreNotificationsList() {
     //load more content
     //commonModel.getMoreNotifications()...
   }
}

In the following piece of code, you can see the View class, in which we work with UI. From here, we send events to Presenter for all further processing.

public class NotificationsView implements NotificationsContract.View {

   private BaseActivity activity;
   private View view;
   private EventBus eventBus;
   //...
   //other views

   public NotificationsView(BaseActivity activity, View view, EventBus eventBus) {
       this.activity = activity;
       this.eventBus = eventBus;
       this.view = view;
       init();
   }

   private void init() {
     //views initialization
   }

  @Override
   public void progressDialog(boolean show) {
       if (show) {
          // showProgressDialog();
       } else {
           //dismissProgressDialog();
       }
   }

   @Override
   public void setNotificationsContent(List<Notification> dataList) {
      //setting adapter data
   }

   @Override
   public NotificationsAdapter getNotificationsAdapter() {
       return notificationsAdapter;
   }

   @Override
   public void showError(String message) {
       //show Toast
   }

   @Override
   public void listItemClickAction(NotificationsAdapter.OnItemClickCallback onItemClickCallback) {
       notificationsAdapter.setOnItemClickCallback(onItemClickCallback);
   }

   @Override
   public void loadMoreNotificationsListAction(NotificationsAdapter.OnLoadMoreListener onLoadMoreListener) {
       notificationsAdapter.setOnLoadMoreListener(onLoadMoreListener);
   }

}

The next snippet presents Model. Here, we request data with the help of ApplicationAPI. We’re not going to cover the implementation of RestService here, as it’s a standard initialization of Retrofit on the base of OkHttpClient + Interceptors, Headers, etc. Also, we’re using a combination of Rx + Retrofit.

public class CommonModel {

   private ApplicationAPI applicationApi;

   public CommonModel() {
       applicationApi = RestService.createRestService();
   }

   public Observable<NotificationsResponse> getNotifications() {
       return applicationApi.getNotifications()
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread());
   }

   public Observable<NotificationsResponse> getMoreNotifications(String page) {
      return applicationApi.getMoreNotifications(page)
              .subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread());
   }

}

Finally, the last code snippet shows us the usual API interface, one based on Retrofit + Rx. public interface ApplicationAPI {

@GET("/myapi/notifications/list/")
   Observable<NotificationsResponse> getNotifications();

   @GET("/myapi/notifications/list/")
   Observable<NotificationsResponse> getMoreNotifications(@Query("page") String page);

   //…
   //other API calls

}

And here you have it - one-among-many implementation of the MVP architecture. You can see that UI and presentation logic are clearly separated. By adding new features, we expand Contract, Presenter, Model (if necessary) and implement the added abilities to View. Each screen would be then divided into such components, making the architecture modular, clean and flexible.

MVP architecture is probably the best type of architecture available today for developing Android applications. If you want to learn more about how your project can benefit from having an MPV-architectured Android app, please feel free to contact us for detailed information.

Alternatively, in case you’re wondering how to use Kotlin in your Java code to build MVP architecture, we welcome you to the first part of our Using Kotlin in Your Production Code segment.