7.
Model View Presenter Theory
Written by Yun Cheng
As you saw in the MVC chapter, although the MVC architecture pattern theoretically allows an app to achieve separation of concerns and unit testability, in practice, MVC didn’t quite work on Android. The problem was that the Android Activity, unfortunately, served the role of both View and Controller. Ideally, you would want to somehow move the Controller out of the Activity into its own class so that the Controller can be unit testable.
One architecture pattern that achieves this goal is MVP, which stands for Model View Presenter, and is made up of the following parts:
- Model is the data layer, responsible for the business logic.
-
View displays the UI and listens to user actions. This is typically an
Activity(orFragment). - Presenter talks to both Model and View and handles presentation logic.
The Model-View-Presenter pattern
With MVP, the Model remains the same as with the MVC architecture. The Model is the data layer, and is responsible for the business logic: retrieving the data, storing the data and changing the data. In your sample project, your business logic revolves around movies, so the Model is composed of the Movie data object, the LocalDataSource, which interacts with the local database. It is also composed of the RemoteDataSource, which interacts with the Movie Database API over the network.
The View is also the same as with the MVC architecture, responsible for displaying the UI, except that, with MVP, this role is specifically designated to an Activity or Fragment. The View will hide and display views, handle navigating to other Activities through intents, and listen for operating system interactions and user input.
The Presenter is the glue class that talks to both the Model and View. Any code that does not directly handle UI or other Android framework-specific logic should be moved out of the View and into the Presenter class. For example, while the View will listen for button clicks, the presentation logic of what happens after a user clicks a button should go into the Presenter class. Similarly, when the Model has updated, it is not the responsibility of the Model to know how the View ultimately wants to display the data.
It is the Presenter’s job to do any additional mapping or formatting of the data before handing it to the View to display it. This kind of logic is known as presentation logic, and it is handled by the aptly named Presenter.
While the View extends from san Activity or Fragment, the Model and Presenter do not extend Android framework-specific classes, and, for the most part, they should not contain Android framework-specific classes. In other words, there should be no references to the com.android.* package in the Model or Presenter. As you will read later, this rule allows for both the Model and the Presenter to be unit tested with this pattern.
In summary, with the MVP pattern, the roles of the Model and the View are equivalent to those in MVC, except that now the Activity or Fragment is explicitly designated the role of the View. The Controller has now been replaced with a Presenter class, which, based on the definition above, seems to serve a similar role as the theoretical Controller.
However, there are several key characteristics of the MVP pattern that differentiate it from MVC and allow for it to work in Android: the order in which events occur, the breaking of the ties between View and Model and the use of interfaces.
The MVP flow
To use MVP, you’ll need to reimagine the order in which events occur in the flow of an Android app. Unlike in MVC wherein the Controller is supposed to be the main entry point for the app, the main entry point is the View.
If the View serves as the entry point for user input — such as a button tap — that implies that the View must be an Activity or Fragment, as these Android classes are able to handle user input. The View can then inform the Presenter of the user input, and the Presenter can handle it by updating the Model. Once the Model has been updated, the Presenter can then update the View with the most up-to-date data.
For example, in your movie app, the user adds a new movie to the To-Watch list by entering the title and release year of the movie, and then they tap a button to add. In Step One, the Activity listens for user inputs and tells the Presenter that the user tapped the button:
fun onClickAddMovie(view: View) {
...
presenter.addMovie(title, releaseDate, posterPath)
}
In Step Two, the Presenter updates the Model (here, served by the localDataSource) by giving it the new Movie to insert into the database:
override fun addMovie(title: String, releaseDate: String, posterPath: String) {
val movie = Movie(title, releaseDate, posterPath)
localDataSource.insert(movie)
...
}
Once the data in the Model has been updated, the Presenter then updates the View in Step Three. In this particular example of adding a new movie, the Presenter simply instructs the View to finish and return to the main screen:
override fun addMovie(title: String, releaseDate: String, posterPath: String) {
val movie = Movie(title, releaseDate, posterPath)
dataSource.insert(movie)
view.returnToMain()
}
Separation of concerns and unit testing
As the example above demonstrates, the Presenter serves as a middleman between the Model and the View, shuttling information and updates between the two classes. Because the Presenter handles these responsibilities, the Model and the View do not need to be aware of each other. By breaking the direct tie between the Model and the View, MVP allows for better separation of concerns.
Furthermore, with this pattern, the Presenter can now be unit tested. Because it does not extend any class specific to the Android framework, the Presenter can be instantiated through a constructor like var presenter = Presenter(), allowing us to call methods on an instance of the Presenter like presenter.onDeleteTapped(moviesToDelete) to test that it behaves as expected in unit tests.
Using interfaces
With the exception of Android Architecture Component classes, which are allowable in Presenters, the Presenter should not contain references to any Android framework-specific classes, such as Context, View, or Intent. This rule allows you to write regular JUnit tests on the Presenter. However, the Presenter, of course, needs to talk to the View, which means it needs a reference to the Activity, an Android framework-specific class. How can you get around this reference to an Android class when you write your unit tests?
The way to resolve this problem is to create interfaces for the Presenter and View, keeping the interfaces in a single class called the Contract class. The Presenter class implements the PresenterInterface and the Activity implements the ViewInterface.
The Presenter class will then hold a reference to an instance of the ViewInterface interface, rather than a reference directly to the Activity. This way, the Presenter and View interact with each other through interfaces rather than actual implementations, which is, in general, a good software engineering principle that allows decoupling of the two classes. In this case, it also has the additional benefits of removing the Android-specific framework class Activity from the Presenter and allows you to mock the ViewInterface in the Presenter for unit testing.
While it is necessary to create an interface for the View, strictly speaking, the same is not true of the Presenter interface. It would be perfectly fine to allow the View to interact with the actual implementation of the Presenter, especially since it is unlikely to have changing implementations of the Presenter. However, you choose to have a Presenter interface in this project so that the Contract class neatly documents the relationship between the Presenter and the View, clearly outlining the interactions.
MVP advantages and concerns
By dividing an Activity into separate Model, View, and Presenter classes with interfaces, you are able to achieve separation of concerns as well as unit-testable Models and Presenters. These are certainly considerable achievements, even if it means having to navigate through interfaces, which can be confusing at first. In Android Studio, CMD + OPTION + B on Mac or CTRL + ALT + B on PC is the keyboard shortcut for finding your way to actual implementations of interface methods.
Additionally, it is important to give some thought to what happens when the operating system destroys the Activity. When your Activity is destroyed, you must be sure to have your Presenter destroy any subscriptions or AsyncTasks or else that will cause problems when the task completes and the Activity is no longer there.
You’ll also need to think about what happens to the Presenter upon destruction of the Activity. If you want the memory held by the Presenter to be freed and garbage-collected when the Activity is destroyed, you must make sure there are no references to the Presenter in any other classes that will continue to live in memory.
If you do want your presenter survive Activity destruction, you can try to save some state through the onSaveInstanceState, or use loaders, which survive configuration changes. In our example project, we will allow the Presenter to be destroyed when the Activity is destroyed.
Key points
-
MVP stands for Model-View-Presenter.
-
MVP is an architecture pattern whose main objective is the separation of concerns and increased unit testability.
-
Unlike MVC wherein the main entry point is the Controller, in MVP the main entry point is the View.
-
The Model is the data layer that handles business logic.
-
The View displays the UI and informs the Presenter about user actions.
-
The View extends
ActivityorFragment. -
The Presenter tells the Model to update the data and tells the View to update the UI.
-
The Presenter should not contain Android framework-specific classes.
-
The Presenter and the View interact with each other through interfaces.
Where to go from here?
In the next chapter, you will apply your knowledge by rewriting the Movies app using MVP. The exercise will set you up for writing more unit tests for the app that previously were not possible without this architecture pattern.