1. Code
  2. Coding Fundamentals
  3. Design Patterns

How to Adopt Model View Presenter on Android

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.
Scroll to top
This post is part of a series called How to Adopt Model View Presenter on Android.
An Introduction to Model View Presenter on Android
Testing and Dependency Injection With Model View Presenter on Android

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.

Model View Presenter LayersModel View Presenter LayersModel View Presenter Layers

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.

App LayoutApp LayoutApp Layout

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 Action DiagramMVP Action DiagramMVP Action Diagram

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
MVP InterfacesMVP InterfacesMVP Interfaces

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.

1
public interface MVP_Main {
2
    /**

3
     * Required View methods available to Presenter.

4
     * A passive layer, responsible to show data

5
     * and receive user interactions

6
     */
7
    interface RequiredViewOps {
8
    	// View operations permitted to Presenter

9
		Context getAppContext();
10
        	Context getActivityContext();
11
		void notifyItemInserted(int layoutPosition);
12
        	void notifyItemRangeChanged(int positionStart, int itemCount);
13
    }
14
15
    /**

16
     * Operations offered to View to communicate with Presenter.

17
     * Processes user interactions, sends data requests to Model, etc.

18
     */
19
    interface ProvidedPresenterOps {
20
       	// Presenter operations permitted to View

21
		void clickNewNote(EditText editText);
22
            // setting up recycler adapter

23
            int getNotesCount();
24
            NotesViewHolder createViewHolder(ViewGroup parent, int viewType);
25
            void bindViewHolder(NotesViewHolder holder, int position);
26
    }
27
28
    /**

29
     * Required Presenter methods available to Model.

30
     */
31
    interface RequiredPresenterOps {
32
       	// Presenter operations permitted to Model

33
		Context getAppContext();
34
        	Context getActivityContext();
35
    }
36
37
    /**

38
     * Operations offered to Model to communicate with Presenter

39
     * Handles all data business logic.

40
     */
41
    interface ProvidedModelOps {
42
        	// Model operations permitted to Presenter

43
            int getNotesCount();
44
            Note getNote(int position);
45
		int insertNote(Note note);
46
        	boolean loadData();
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.

1
public class MainActivity
2
        extends AppCompatActivity
3
    implements View.OnClickListener, MVP_Main.RequiredViewOps {
4
5
    private MVP_Main.ProvidedPresenterOps mPresenter;
6
    private EditText mTextNewNote;
7
    private ListNotes mListAdapter;
8
9
    @Override
10
    public void onClick(View v) {
11
        switch (v.getId()) {
12
            case R.id.fab:{
13
                // Adds a new note

14
                mPresenter.clickNewNote(mTextNewNote);
15
            }
16
        }
17
    }
18
    @Override
19
    public Context getActivityContext() {
20
        return this;
21
    }
22
23
    @Override
24
    public Context getAppContext() {
25
        return getApplicationContext();
26
    }
27
    // Notify the RecyclerAdapter that a new item was inserted

28
    @Override
29
    public void notifyItemInserted(int adapterPos) {
30
        mListAdapter.notifyItemInserted(adapterPos);
31
    }
32
    // notify the RecyclerAdapter that items has changed

33
    @Override
34
    public void notifyItemRangeChanged(int positionStart, int itemCount){
35
        mListAdapter.notifyItemRangeChanged(positionStart, itemCount);
36
    }
37
    // notify the RecyclerAdapter that data set has changed

38
    @Override
39
    public void notifyDataSetChanged() {
40
        mListAdapter.notifyDataSetChanged();
41
    }
42
    // Recycler adapter

43
    // This class could have their own Presenter, but for the sake of

44
    // simplicity, will use only one Presenter.

45
    // The adapter is passive and all the processing occurs 

46
    // in the Presenter layer.

47
    private class ListNotes extends RecyclerView.Adapter<NotesViewHolder>       
48
    {
49
        @Override
50
        public int getItemCount() {
51
            return mPresenter.getNotesCount();
52
        }
53
54
        @Override
55
        public NotesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
56
            return mPresenter.createViewHolder(parent, viewType);
57
        }
58
59
        @Override
60
        public void onBindViewHolder(NotesViewHolder holder, int position) {
61
            mPresenter.bindViewHolder(holder, position);
62
        }
63
    }
64
}

Presenter Layer

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.

1
public class MainPresenter implements MVP_Main.ProvidedPresenterOps, MVP_Main.RequiredPresenterOps {
2
3
    // View reference. We use as a WeakReference

4
    // because the Activity could be destroyed at any time

5
    // and we don't want to create a memory leak

6
    private WeakReference<MVP_Main.RequiredViewOps> mView;
7
    // Model reference

8
    private MVP_Main.ProvidedModelOps mModel;
9
10
    /**

11
     * Presenter Constructor

12
     * @param view  MainActivity

13
     */
14
    public MainPresenter(MVP_Main.RequiredViewOps view) {
15
        mView = new WeakReference<>(view);
16
    }
17
18
    /**

19
     * Return the View reference.

20
     * Throw an exception if the View is unavailable.

21
     */
22
    private MVP_Main.RequiredViewOps getView() throws NullPointerException{
23
        if ( mView != null )
24
            return mView.get();
25
        else
26
            throw new NullPointerException("View is unavailable");
27
    }
28
29
    /**

30
     * Retrieves total Notes count from Model

31
     * @return  Notes list size

32
     */
33
    @Override
34
    public int getNotesCount() {
35
        return mModel.getNotesCount();
36
    }
37
38
    /**

39
     * Creates the RecyclerView holder and setup its view

40
     * @param parent    Recycler viewGroup

41
     * @param viewType  Holder type

42
     * @return          Recycler ViewHolder

43
     */
44
    @Override
45
    public NotesViewHolder createViewHolder(ViewGroup parent, int viewType) {
46
        NotesViewHolder viewHolder;
47
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
48
49
        View viewTaskRow = inflater.inflate(R.layout.holder_notes, parent, false);
50
        viewHolder = new NotesViewHolder(viewTaskRow);
51
52
        return viewHolder;
53
    }
54
55
    /**

56
     * Binds ViewHolder with RecyclerView

57
     * @param holder    Holder to bind

58
     * @param position  Position on Recycler adapter

59
     */
60
    @Override
61
    public void bindViewHolder(final NotesViewHolder holder, int position) {
62
        final Note note = mModel.getNote(position);
63
        holder.text.setText( note.getText() );
64
        holder.date.setText( note.getDate() );
65
        holder.btnDelete.setOnClickListener(new View.OnClickListener() {
66
            @Override
67
            public void onClick(View v) {
68
                clickDeleteNote(note, holder.getAdapterPosition(), holder.getLayoutPosition());
69
            }
70
        });
71
72
    }
73
74
    /**

75
     * @return  Application context

76
     */
77
    @Override
78
    public Context getAppContext() {
79
        try {
80
            return getView().getAppContext();
81
        } catch (NullPointerException e) {
82
            return null;
83
        }
84
    }
85
86
    /**

87
     * @return  Activity context

88
     */
89
    @Override
90
    public Context getActivityContext() {
91
        try {
92
            return getView().getActivityContext();
93
        } catch (NullPointerException e) {
94
            return null;
95
        }
96
    }
97
98
    /**

99
     * Called by View when user clicks on new Note button.

100
     * Creates a Note with text typed by the user and asks

101
     * Model to insert it in DB.

102
     * @param editText  EditText with text typed by user

103
     */
104
    @Override
105
    public void clickNewNote(final EditText editText) {
106
        getView().showProgress();
107
        final String noteText = editText.getText().toString();
108
        if ( !noteText.isEmpty() ) {
109
            new AsyncTask<Void, Void, Integer>() {
110
                @Override
111
                protected Integer doInBackground(Void... params) {
112
                    // Inserts note in Model, returning adapter position

113
                    return mModel.insertNote(makeNote(noteText));
114
                }
115
116
                @Override
117
                protected void onPostExecute(Integer adapterPosition) {
118
                    try {
119
                        if (adapterPosition > -1) {
120
                            // Note inserted

121
                            getView().clearEditText();
122
                            getView().notifyItemInserted(adapterPosition + 1);
123
                            getView().notifyItemRangeChanged(adapterPosition, mModel.getNotesCount());
124
                        } else {
125
                            // Informs about error

126
                            getView().hideProgress();
127
                            getView().showToast(makeToast("Error creating note [" + noteText + "]"));
128
                        }
129
                    } catch (NullPointerException e) {
130
                        e.printStackTrace();
131
                    }
132
                }
133
            }.execute();
134
        } else {
135
            try {
136
                getView().showToast(makeToast("Cannot add a blank note!"));
137
            } catch (NullPointerException e) {
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
    public Note makeNote(String noteText) {
149
        Note note = new Note();
150
        note.setText( noteText );
151
        note.setDate(getDate());
152
        return note;
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.

1
public class MainModel implements MVP_Main.ProvidedModelOps {
2
3
    // Presenter reference

4
    private MVP_Main.RequiredPresenterOps mPresenter;
5
    private DAO mDAO;
6
    // Recycler data

7
    public ArrayList<Note> mNotes;
8
9
    /**

10
     * Main constructor, called by Activity during MVP setup

11
     * @param presenter Presenter instance

12
     */
13
    public MainModel(MVP_Main.RequiredPresenterOps presenter) {
14
        this.mPresenter = presenter;
15
        mDAO = new DAO( mPresenter.getAppContext() );
16
    }
17
18
     /**

19
     * Inserts a note on DB

20
     * @param note  Note to insert

21
     * @return      Note's position on ArrayList

22
     */
23
    @Override
24
    public int insertNote(Note note) {
25
        Note insertedNote = mDAO.insertNote(note);
26
        if ( insertedNote != null ) {
27
            loadData();
28
            return getNotePosition(insertedNote);
29
        }
30
        return -1;
31
    }
32
33
     /**

34
     * Loads all Data, getting notes from DB

35
     * @return  true with success

36
     */
37
    @Override
38
    public boolean loadData() {
39
        mNotes = mDAO.getAllNotes();
40
        return mNotes != null;
41
    }
42
     /**

43
     * 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
    public Note getNote(int position) {
49
        return mNotes.get(position);
50
    }
51
     
52
    /**

53
     * Get ArrayList size

54
     * @return  ArrayList size

55
     */
56
    @Override
57
    public int getNotesCount() {
58
        if ( mNotes != null )
59
            return mNotes.size();
60
        return 0;
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
private void setupMVP() {
5
     // Create the Presenter

6
     MainPresenter presenter = new MainPresenter(this);
7
     // Create the Model

8
     MainModel model = new MainModel(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.

View LifecycleView LifecycleView Lifecycle
1
public class MainActivity
2
        extends AppCompatActivity
3
    implements View.OnClickListener, MVP_Main.RequiredViewOps
4
{
5
    // …

6
    private void setupMVP() {
7
        // Check if StateMaintainer has been created

8
        if (mStateMaintainer.firstTimeIn()) {
9
            // Create the Presenter

10
            MainPresenter presenter = new MainPresenter(this);
11
            // Create the Model

12
            MainModel model = new MainModel(presenter);
13
            // Set Presenter model

14
            presenter.setModel(model);
15
            // Add Presenter and Model to StateMaintainer

16
            mStateMaintainer.put(presenter);
17
            mStateMaintainer.put(model);
18
19
            // Set the Presenter as a interface

20
            // To limit the communication with it

21
            mPresenter = presenter;
22
23
        }
24
        // get the Presenter from StateMaintainer

25
        else {
26
            // Get the Presenter

27
            mPresenter = mStateMaintainer.get(MainPresenter.class.getName());
28
            // Updated the View in Presenter

29
            mPresenter.setView(this);
30
        }
31
    }
32
    // …

33
}

Conclusion

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.