In the previous tutorial, we talked about the Model View Presenter pattern, how it is applied on Android, and what its most important advantages are. In this tutorial, we explore the Model View Presenter pattern in more detail by implementing it in an Android application.
In the previous tutorial, we talked about the Model View Presenter pattern, how it is applied on Android, and what its most important advantages are. In this tutorial, we explore the Model View Presenter pattern in more detail by implementing it in an Android application.
In this tutorial:
we build a simple application using the MVP pattern
we explore how to implement the MVP pattern on Android
and we discuss how to overcome some difficulties caused by Android's architecture
1. Model View Presenter
The Model View Presenter pattern is an architectural pattern based on the Model View Controller (MVC) pattern that increases the separation of concerns and facilitates unit testing. It creates three layers, Model, View, and Presenter, each with a well defined responsibility.
The Model holds the business logic of the application. It controls how data is created, stored, and modified. The View is a passive interface that displays data and routes user actions to the Presenter. The Presenter acts as the middleman. It retrieves data from the Model and shows it in the View. It also processes user actions forwarded by the View.
2. Project Planning & Setup
We are going to build a simple notes application to illustrate MVP. The app allows the user to take notes, save them in a local database, and delete notes. To make it simple, the app will have only one Activity.
In this tutorial, we concentrate primarily on the implementation of the MVP pattern. Other functions, such as setting up a SQLite database, constructing a DAO, or handling user interaction, are skipped. If you need help with any of these topics, Envato Tuts+ has some great tutorials about these topics.
Action Diagram and MVP Layers
Let's start with the creation of a new note. If we break this action into smaller operations, then this is what the flow would look like using the MVP architectural pattern:
The user types a note and clicks the add note button.
The Presenter creates a Note object with the text entered by the user and asks the Model to insert it in the database.
The Model inserts the note in the database and informs the Presenter that the list of notes has changed.
The Presenter clears the text field and asks the View to refresh its list to show the newly created note.
MVP Interfaces
Let's now consider the operations needed to achieve this action and separate them using MVP. To keep the various objects loosely coupled, the communication between the layers takes places by using interfaces. We need four interfaces:
RequiredViewOps: required View operations available to Presenter
ProvidedPresenterOps: operations offered to View for communication with Presenter
RequiredPresenterOps: required Presenter operations available to Model
ProvidedModelOps: operations offered to Model to communicate with Presenter
3. Implementing MVP on Android
Now that we have an idea of how the various methods should be organized, we can start creating our app. We simplify the implementation by only focusing on the action to add a new note. The source files of this tutorial are available on GitHub.
We use only one Activity with a layout that includes:
EditText for new notes
Button to add a note
RecyclerView to list all notes
two TextView elements and a Button inside a RecyclerView holder
Interfaces
Let’s begin by creating the interfaces. To keep everything organized, we place the interfaces within a holder. Again, in this example we focus on the action to add a new note.
* Operations offered to Model to communicate with Presenter
39
* Handles all data business logic.
40
*/
41
interfaceProvidedModelOps{
42
// Model operations permitted to Presenter
43
intgetNotesCount();
44
NotegetNote(intposition);
45
intinsertNote(Notenote);
46
booleanloadData();
47
}
48
}
View Layer
It's now time to create the Model, View, and Presenter layers. Since MainActivity will act as the View, it should implement the RequiredViewOps interface.
The Presenter is the middleman and needs to implement two interfaces:
ProvidedPresenterOps to allow calls from the View
RequiredPresenterOps to receive results from the Model
Pay special attention to the View layer reference. We need to use a WeakReference<MVP_Main.RequiredViewOps> since MainActivity could be destroyed at any time and we want to avoid memory leaks. Also, the Model layer hasn't been set up yet. We do that later when we connect the MVP layers together.
getView().showToast(makeToast("Cannot add a blank note!"));
137
}catch(NullPointerExceptione){
138
e.printStackTrace();
139
}
140
}
141
}
142
143
/**
144
* Creates a Note object with given text
145
* @param noteText String with Note text
146
* @return A Note object
147
*/
148
publicNotemakeNote(StringnoteText){
149
Notenote=newNote();
150
note.setText(noteText);
151
note.setDate(getDate());
152
returnnote;
153
154
}
155
}
Model Layer
The Model layer is responsible for handling the business logic. It holds an ArrayList with the notes added to the database, a DAO reference to perform database operations, and a reference to the Presenter.
* Gets a specific note from notes list using its array position
44
* @param position Array position
45
* @return Note from list
46
*/
47
@Override
48
publicNotegetNote(intposition){
49
returnmNotes.get(position);
50
}
51
52
/**
53
* Get ArrayList size
54
* @return ArrayList size
55
*/
56
@Override
57
publicintgetNotesCount(){
58
if(mNotes!=null)
59
returnmNotes.size();
60
return0;
61
}
62
}
4. Tying Everything Together
With the MVP layers in place, we need to instantiate them and insert the necessary references. Before we do, we need to address a few issues that are directly related to Android.
Instantiating the Layers
Because Android doesn't permit the instantiation of an Activity, the View layer will be instantiated for us. We are responsible for instantiating the Presenter and Model layers. Unfortunately, instantiating those layers outside the Activity can be problematic.
It is recommended to use a form of dependency injection to accomplish this. Since our goal is to concentrate on the MVP implementation, we will take an easier approach. This isn't the best approach available, but it is the easiest to understand. We’ll discuss MVP and dependency injection later in this series.
instantiate the Presenter and Model in the Activity using local variables
set up RequiredViewOps and ProvidedModelOps in the Presenter
set up RequiredPresenterOps in the Model
save ProvidedPresenterOps as a reference to use in the View
1
/**
2
* Setup Model View Presenter pattern
3
*/
4
privatevoidsetupMVP(){
5
// Create the Presenter
6
MainPresenterpresenter=newMainPresenter(this);
7
// Create the Model
8
MainModelmodel=newMainModel(presenter);
9
// Set Presenter model
10
presenter.setModel(model);
11
// Set the Presenter as a interface
12
mPresenter=presenter;
13
}
Handling Configuration Changes
Another thing we should consider is the Activity’s lifecycle. Android’s Activity could be destroyed at any time and the Presenter and Model layers could also be destroyed with it. We need to fix this by using some kind of state machine to save state during configuration changes. We should also inform the other layers about the Activity’s state.
To accomplish this, we’ll use a separate class, StateMaintainer, that contains a Fragment that maintains its state and uses this fragment to save and retrieve our objects. You can take a look at the implementation of this class in the source files of this tutorial.
We need to add a onDestroy method to the Presenter and the Model to inform them about the Activity’s current state. We also need to add a setView method to the Presenter, which will be responsible for receiving a new View reference of the recreated Activity.
The MVP pattern is capable of solving some issues caused by Android’s default architecture. It makes your code easy to maintain and test. Adopting MVP may look difficult at first, but once you understand the logic behind it, the whole process is straightforward.
You are now able to create your own MVP library or use a solution that is already available, such as Mosby or simple-mvp. You should now better understand what these libraries are doing behind the scenes.
We’re almost at the end of our MVP journey. In the third and last part of this series, we’ll add unit testing to the mix and adapt our code to use dependency injection with Dagger. I hope to see you there.