A journey to the world of MVP and Loaders ( Part 1)
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:
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:
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 :
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:
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) :
Presenter will have attachView() and detachView() functiong alongside some other functions to handle UI events from the view. The presenter is as follows:
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.
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 :
And I would love to discuss your thoughts on this. If you like the article, hit that green heart button.