WTF is MVP ?
If you are here searching for answers about Minimum Viable Product or you are here as a result of watching the first episode of the first season of Silicon Valley, this might not be the blog you are looking for. If you are a software engineer and you develop apps (especially on Android), this is a must read. Either way, you can share this blog among other software developers you know! :)
MVP stands for Model, View, Presenter. MVP is a way to abstract or decouple different components to make them independent of each other. This makes the codebase cleaner, improves readability, improves maintainability and also helps in rigorous testing.
Model : Data access layer such as database API or remote server API.
View : Layer that shows/displays data and reacts to user actions. This could be an Activity, Fragment, View or Dialog. This contains almost no logic. Converts presenters commands to UI actions and listens to user actions which are passed to the presenter.
Presenter : Layer that provides View with the data from Model. Presenters essentially sits in between Models and Views.
Why do we need MVP ?
- KISS : Stands for Keep It Simple, Stupid or Keep It Stupid Simple. Don’t fight with the Views. Fight with business logic.
- Decouple : Helps in concentrating on the problem. Helps solving issues like configuration changes, background tasks, etc
- Most problems will be handled by the architecture itself and the app wouldn’t need external libraries to handle specific issues.
- Rigorous testing : Helps in building testable apps by writing automation tests.
How do we develop apps for the next billion users ?
We start defining contracts for each layer. A contract is a class or an agreement. We define contracts for all the layers in the architecture - Model, View and Presenter.
Let's take an example of showing the latest challenges on HackerEarth to users.
ChallengesContract.java class defines two interfaces, one each for the View and the Presenter. The view specific functions and variables are declared in the
ChallengesContract.View interface. This should mostly include functions to update the UI and to listen to user actions.
ChallengesContract.Presenter interface which is defined in the
ChallengesContract.java class declares functions to get data from the remote server and functions to handle user actions like clicks, long clicks, etc.
ChallengesContract.View can be implemented in two Android components - Activity and Fragment. We choose fragments to implement the
ChallengesContract.View for two main reasons :
- Fragments help us in defining layouts for tablet devices.
- As the Fragment is controlled by the Activity, the control of creating the presenter and the view remains with the Activity too.
While the fragment is made visible to the user, we instantiate the presenter object and call the
start() method on the presenter. We used
onStart() and not
onResume() as we don't want users to keep waiting for the updated view while the fragment is actively running in the foreground.
challengesPresenter.start() starts by deleting old data in the cache (if there is any) and makes a request to remote servers and receives the latest challenges on HackerEarth. The presenter handles all the business logic. It requests for data from the Model and provides necessary data to the View. The presenter now needs the Model layer.
Model layer needs a Contract or an agreement. This helps other layers (View and Presenter) to communicate. The
ChallengesPresistenceContract.java defines authorities, MIME types for Uri and the scheme for the all the Uri to be used in the ContentProvider. We define the Entry classes as well. Entry classes are essentially BaseColumns which represents each table. BaseColumns is an interface which defines contsants for the count off rows in the directory/table and for the unique ID for each row in the table.
ChallengeEntry class defines the table name, columns and provides the Uri for the table.
As we know, for any persistence to work we need to define models for each tables. We create immutable final classes for models which represents each table. An immutable model class has its advantages :
- We won't face synchronization issues as immutable objects are thread-safe
- It also helps in parallelization as there are no conflicts between the objects.
- References to to immutable objects will not change and this helps in caching of those objects and reusing them later.
Once we have our models and contracts ready we can go ahead and create tables by using the
SQLiteOpenHelper class. Though
SQLiteOpenHelper helps us in creating and/or upgrading the database for the app, we will have to extend it and provide custom database names, versions and tables.
Creating databases will bring us closer to the core of the Model layer - Data Store or Data Access Object.
I can hear you say : " Hey I have heard about Data Access Object. That is DAO!! Did you use greenDAO?! It's awesome!".
I'm here to tell you that we didn't use greenDAO or any other DAO libraries as we wanted to reduce the apk size as much as possible and reduce the dependencies in the codebase.
DAO abstracts and encapsulates all access to the data source. The data source could be a persistent storage like SQLite database on Android, a remote data store or a cache. This will completely hide the data source implementation details from its clients. The DAO allows to adapt to different data source implementations without affecting other components. The
ChallengesDataStore.java interface will serve as the main entry point for accessing data. We also define callbacks to various operations. The callbacks helps us determine the state of each operation and use them accordingly in the Presenter.
ChallengesDataStore gives us the opportunity to define different implementations for the local/persistent storage and for handling remote server connections. We create two classes for each - One for handling persistent data and another for handling remote data.
ChallengesLocalDataStore.java handles the local persistence and
ChallengesRemoteDataStore.java handles the remote data. Both classes are implementations of
We follow the Repository Model for the Model layer. As Martin Fowler puts it A Repository mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. What this means is that a Repository should be responsible in deciding the source of the data (local or remote) to be used for updating the View through the Presenter.
In the architecture and the use cases we have here, the repository is responsible fetch data from remote server, store data and notify the ContentProvider. The ContentProvider notifies the change in data to the Uri and every client observing that Uri will receive an update. We create
ChallengesRepo.java class which implements
ChallengesDataStore. This will have the logic behind which data source to use and when to use the data. This also helps in creating offline mode in apps. The
ChallengesRepo can also define the callbacks to data availability. This helps in showing relevant views through the presenter.
It's always recommended to have one ContentProvider per app. A single ContentProvider should handle different types of Uri. One can create 2 or more ContentProvider if there are 2 sets of data; one that needs to be shared and another that should not be. We can also have 2 or more ContentProvider per app if there are 2 or more databases. A ContentProvider manages data access for a database to different tables. It helps us in executing CRUD operations as well. ContentProvider manages access to structured set of data. Loaders helps in loading of data and observes data changes to the data
ChallengesAppContentProvider.java here shows the implementation of ContentPovider.
The presenter contains all the business logic. Presenter will never know the source of the data, it's sole responsibility in handling data is to query for data, process data and update the View with the data. It listens to user interactions from the UI like View.OnClickListener, View.OnLongClickListener, View.OnTouchListener, etc. Presenters are also responsible in retrieving data from the repository and updates the UI as required.
ChallengesPresenter.java implements the ChallengesContract.Presenter, ChallengesRemoteDataSource.GetChallengesCallback, ChallengesRepo.LoadDataCallback and LoaderManager.LoaderCallbacks
If you have come this far, you have come far enough! MVP is the first step towards building an app that scales to billion users, helps us developers build testable apps and test on device farms like Amazon Device Farm or Firebase Test Lab and enables to build offline apps.
Stay tuned for more updates! Feel free to comment below or ping us at email@example.com if you have any suggestions!