Disclaimer: This post is quite old and better API's are out there now. Please read up on Android architecture view models.

This post was originally written for Mindorks publication on Medium. This post will be split into two parts. The first part explains the need, the job of each component of MVP and then the actual code to make your project organized in Model-View-Presenter(MVP) pattern. While the second part will focus on Loader API, on how to use it to save your presenters during rotation.

The example project for this post is at this link.

The only-mvp branch of the example project deals with MVP only. The master branch shows an example of MVP and Loader API. We will cover the master branch in the second post.

Let’s understand the need for MVP

We all started with making small projects and as time went on, the complexity of these projects started to increase, bringing in chaos.

“The Only Thing That Is Constant Is Change -”― Heraclitus

And the time to make these changes or new features in our chaotic complex project kept increasing. Also the amount of code which is deleted or shifted or is written new to make these changes kept increasing. And how can we forget about the bugs? Complexity increase brings all kinds of bugs and we first have to locate the bug before we can squash it. But we can’t locate it in that messy code, can we?

We slowly realize how the mixing of different parts of code led to time being wasted on stupid bugs and how hard it made making changes or adding new things making our project unmaintainable. We slowly also realize our mess has been handling all kind of stuff regarding our view, transformation logic, navigation logic and business logic. We can’t even test any logic or view as all of it is too interdependent.

We realize that what we don’t have is separation of concern and it is causing us many kind of problem. Separation of concern means that the concern of how data is shown to the user, how it is fetched and what transformation we applied on it should all be separate so we easily change any of them without worrying about affecting the other one.

This is the need of Model-View-Presenter (MVP) pattern —Separation of concern. Now that we know we need it, we need to understand the job of each component.

Job of Model

This component’s job is to implement the business logic and handling of data. Handling of data refers to the fetching of data( Either from local cache, or from external storage or from server in a network call) and then saving it in memory or cache or external storage.

Let’s take an example to understand it : You have a button in your app and it loads some recipes from the server but only for premium accounts. Now when a user clicks on it, the network request reaches the models which is implementing the business logic, so it first checks with the user data stored if the user has a premium account or not. If the account is not premium, the request is denied and an upgrade dialog is shown. Otherwise, the data is fetched from the server and is saved in the database to be shown instantly the next time. Here is some pseudo code for it for better understanding:

class Model {
    UserModelHelper userModel;
    RecipeModelHelper recipeModel;
    NetworkManager networkManager;
    /*
    Make the constructor here and initialize the model helpers   
    like UserModel and other managers
    */
   
    /* Checks if the account is premium or not. Throws an 
    exception if not, otherwise proceeds with network call.
    The business logic is to only give this option to
    the premium account holders. This type of logic goes
    in the model*/
    List<Recipe> onLoadRecipes() {
        if (userModel.isAccountPremium()) {
            // Fetches the recipes
            Result result = networkManager.getRecipes();
            // Saves the recipes data
            recipeModel.saveRecipes(result.getRecipes());
            // Send the result back to the presenter.
            return result.getRecipes();
        } else {
            /* We can show an upgrade dialog when we catch      
            this */
            throw new UpgradeAccountException();
        }
    }
}

Job of View

This component only job is related to the views that are being displayed. It doesn’t know how they are fetched or even if they are coming from the cache or from a network call. Activity, Fragment etc. components come under this.

Continuing the example, you have a recipe screen where you show the list of recipes or an upgrade dialog. Now this view will have this kind of pseudo code:

class RecipeView {
// Lifecycle and other instantiation events.

/*
updateUI function is called with RecipeData object 
holding the list to be shown 
*/
void updateUI(RecipeData data) {
    if (data.getError()) {
      // show upgrade dialog.
    }
    else {
      recipeListAdapter.set(data.getRecipesList());
    }
  }
}

Job of Presenter

This component is the handler of logic and decides when, what and how to display data. It is responsible for getting the UI events, asking the model to make necessary changes, applying transformation and caching results before sending the data back to the view.

In the example we have seen, the user wanted to fetch the recipes and the presenter asked the model to fetch the recipes. This is the first role of presenter — handle UI events and ask model for data. When those recipes results came back, it cached them, and then shipped it to the view. This is the second role of presenter — cache and send data to be displayed to the View. Maybe the user now applied a filter to the recipes. We have already fetched the whole list of recipes. Do we fetch the filtered recipes again? No, we will simply apply a transformation filter on the cached recipes. This is the third role of presenter — transform data if needed. Here is some pseudo code for it :

class RecipePresenter {
  Model model;
  View view;
  List<Recipe> list;
  RecipeData data;
  // Make the constructor here and initialize the model and view
  
  /* First and second role of presenter- asking model for data  
  on button click. Caching the recipes in 'data' variable and 
  then sending the results back */
  void loadRecipes() {
    try {
      list = model.onLoadRecipes();
      data.setRecipesList(list);
    } catch (UpgradeAccountException exception) {
      data.setError(true);
    }
    view.updateUI(data);
  }
  // Third role of presenter - transform data
  void loadFilterRecipe() {
    List<Recipe> filterList = list.someFilter();
    data.setRecipesList(filterList);
    view.updateUI(data);
  }
}

Implementation of MVP

Now that you understand what is the job of each MVP component, lets dive into how we actually implement it in code.

Suppose we have a Activity acting as our view. First we make the presenter object in our activity onCreate(). To connect the presenter and the view, we use an interface. The interface defines all the functions that presenter can call on the view and they will all be somehow to update the UI. The interface is as follows:

interface MyViewInterface {
        /* MyState class is a class which contains all the data needed 
        to display the UI of the said activity. */
        void updateUI(MyState state);
}

We implement it in our activity so that presenter can send UI updates to us. We say that we attach the view in onResume() to the presenter, i.e. we pass an interface implementation asking the presenter to give us UI updates on this. And in onPause(), we set it to null as we can’t display UI updates anymore, hence we don’t need UI updates from presenter. The code for the view (in this case an activity is as follows) :

public class MyActivity extends AppCompatActivity implements MyViewInterface {
    private MyPresenter presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        presenter = new MyPresenter(DataManager.getDataManager());
    }
    @Override
    protected void onStart() {
        super.onStart();
        // instantiates views and sets on click on them.
        initView();
    }
    @Override
    protected void onPause() {
        // Detach the view as UI isn't going to be visible
        presenter.detachView();
        super.onPause();
    }
    @Override
    protected void onResume() {
        super.onResume();
        // Attach the view interface as we want UI updated again.
        presenter.attachView(this);
    }
    private void initView() {
        // initialize views and set on click and other stuff here
      
        /* the following line isn't syntactically correct but I 
           just want to give an idea */
        submitButton.setOnClickListener(presenter.saveName(name));
    }
    @Override
    public void updateUI(MyState state) {
       /* Update the UI using the MyState object*/
    }
}

Presenter will have attachView() and detachView() functiong alongside some other functions to handle UI events from the view. The presenter is as follows:

class MyPresenter {
    private MyViewInterface viewInterface;
    private DataManager dataManager;
    private MyState state;
    
    MyPresenter(DataManager dataManager) {
        this.dataManager = dataManager;
        /* Initiates state with a default value for the initial UI 
           of this view */
        state = new MyState("This is the template data");
    }
    
    /**
     * Called when the UI became visible in onResume, thus we   
       save the interface object and update the UI with the cached 
       state */
    void attachView(MyViewInterface viewInterface) {
        this.viewInterface = viewInterface;
        viewInterface.updateUI(state);
    }
    
    /**
     * Called when the UI became invisible, thus we set the
       interface object to null as we can't dispatch more UI 
       updates but we keep our state object up to date with 
       the model changes and will update the UI when it attaches
       again. */
    void detachView() {
        this.viewInterface = null;
    }
    
    /* Button click UI event from submitButton in view to save   
       data in the model which we do using dataManager */
    void saveName(String name) {
        dataManager.saveName(name);
        state.setNameSaved(name);
        viewInterface.updateUI(state);
    }
}

And DataManager is the class which acts as the interface for every presenter to interact with the model. In my opinion it should be kept a singleton. You can use the lazy instantiation method to instantiate it with application context in the application class.

//MyApplication.java

//onCreate()
DataManager.init(this.getApplicationContext());

To access it we can then use the public static method in DataManager called getDataManager() which returns the singleton reference. The Model pseudo code that we saw when understanding the job of Model component will actually be present in DataManager class. It also delegates the tasks to other managers, for example: network requests for model are delegated to NetworkManager, SharedPreference changes to SharedPreferencesHelper etc. It is also responsible for checking that if we have cache data, then update the UI with it while proceeding with network call, and when the network call returns, update the database and cache , and update the UI again. The sample code for the data manager :

public class DataManager {
    private static DataManager dataManager;
    private final Context context;
    private SharedPreferencesHelper sharedPreferencesHelper;
  
    private DataManager(Context context) {
        this.context = context;
        /* Create your delegation managers here like 
          NetworkManager, SharedPreferencesHelper etc. */
        sharedPreferencesHelper = new SharedPreferencesHelper();
    }
    
    public static void init(Context context) {
        dataManager = new DataManager(context);
    }
    
    public static DataManager getDataManager(){
        return dataManager;
    }
    
    /**
     * Change the name in the appropriate model. It can be
       shared preferences, or SQL db or Realm db or making a   
       network call (Dispatched via Network Manager) to save the 
       name in server. Here I'm using a shared preference as an 
       example */
    public void saveName(String name) {
        sharedPreferencesHelper.saveName(context, name);
    }
}

Please check out the example project for full code implementation. The next part will cover Loader API and rotation problem.

Source code

  1. Part 1 MVP Pattern

Further reading

  1. Yet another MVP article — Part 1: Lets get to know the project