In this tutorial, we’ll be discussing the android MVP principles and develop an application based on it. Since the start, we’ve been developing applications by adding all the business logic inside the Activity. Let’s analyze the cons of our current approach before digging into the new principles.
Android MVP
With our current approach, the MainActivity class contains all the implementation logic of our application. We’ve been using stuff ranging from Retrofit callbacks to data models(SharedPref, POJO classes) all inside the Activity class.
Eventually, our Activities become god classes and cause problems in maintainability, readability, scalability and refactoring an already bloated code.
Unit testing gets tough since the implementation logic is tightly coupled with the Android APIs. This is where MVP ( Model View Presenter) comes in handy. It allows us to write a clean and flexible code base while giving the luxury to switch any part of the code without much hassle.
Model View Presenter
Model View Presenter divides our application into three layers namely the Model, View and Presenter.
- Model: This handles the data part of our application
- Presenter: It acts as a bridge that connects a Model and a View.
- View: This is responsible for laying out views with the relevant data as instructed by the Presenter
Note: The View never communicates with Model directly.
Android MVP Architecture
The diagram below depicts a basic MVP structure.
Android MVP Guidelines
- Activity, Fragment and a CustomView act as the View part of the application.
- The Presenter is responsible for listening to user interactions (on the View) and model updates (database, APIs) as well as updating the Model and the View.
- Generally, a View and Presenter are in a one to one relationship. One Presenter class manages one View at a time.
- Interfaces need to be defined and implemented to communicate between View-Presenter and Presenter-Model.
- The Presenter is responsible for handling all the background tasks. Android SDK classes must be avoided in the presenter classes.
- The View and Model classes can’t have a reference of one another.
Having covered the theory of MVP architecture, let’s build an android MVP app now.
Android MVP Example App Project Structure
The android mvp project consists of 3 interface files (also known as contracts). The Impl files are where the interfaces are implemented.
We’ll be creating a single activity application that’ll display a random quote from a list of quotes present in an ArrayList. We’ll see how the presenter manages to keep the business logic of the application away from the activity class.
Note: Interactors are classes built for fetching data from your database, web services, or any other data source.
Android MVP app code
The code for the activity_main.xml
layout is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.journaldev.hellomvp.MainActivity"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:text="Implementing MVP Pattern in this Application." app:layout_constraintBottom_toBottomOf="parent" android:padding="8dp" android:gravity="center" android:textAppearance="?android:attr/textAppearanceSearchResultTitle" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:id="@+id/textView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="GET NEXT QUOTE" android:layout_margin="@android:dimen/notification_large_icon_height" app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent"/> </android.support.constraint.ConstraintLayout> |
The code for the GetQuoteInteractor
interface is given below.
1 2 3 4 5 6 7 8 9 |
package com.journaldev.hellomvp; public interface GetQuoteInteractor { interface OnFinishedListener { void onFinished(String string); } void getNextQuote(OnFinishedListener listener); } |
It contains a nested interface onFinishedListener. We’ll be using a handler inside our GetQuoteInteractorImpl.java
below. On completion of the handler, the above onFinished
method would be triggered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.journaldev.hellomvp; import android.os.Handler; import java.util.Arrays; import java.util.List; import java.util.Random; public class GetQuoteInteractorImpl implements GetQuoteInteractor { private List<String> arrayList = Arrays.asList( "Be yourself. everyone else is already taken.", "A room without books is like a body without a soul.", "You only live once, but if you do it right, once is enough.", "Be the change that you wish to see in the world.", "If you tell the truth, you don't have to remember anything." ); @Override public void getNextQuote(final OnFinishedListener listener) { new Handler().postDelayed(new Runnable() { @Override public void run() { listener.onFinished(getRandomString()); } }, 1200); } private String getRandomString() { Random random = new Random(); int index = random.nextInt(arrayList.size()); return arrayList.get(index); } } |
The MainView.java
interface is defined below.
1 2 3 4 5 6 7 8 |
package com.journaldev.hellomvp; public interface MainView { void showProgress(); void hideProgress(); void setQuote(String string); } |
showProgress()
and hideProgress()
would be used for displaying and hiding the progressBar while the next random quote is fetched from the GetQuoteInteractorImpl class.
setQuote()
will set the random string on the textView.
The code for the MainPresenter.java
interface is given below.
1 2 3 4 5 6 7 |
package com.journaldev.hellomvp; public interface MainPresenter { void onButtonClick(); void onDestroy(); } |
The MainPresenterImpl.java
class is defined below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package com.journaldev.hellomvp; public class MainPresenterImpl implements MainPresenter, GetQuoteInteractor.OnFinishedListener { private MainView mainView; private GetQuoteInteractor getQuoteInteractor; public MainPresenterImpl(MainView mainView, GetQuoteInteractor getQuoteInteractor) { this.mainView = mainView; this.getQuoteInteractor = getQuoteInteractor; } @Override public void onButtonClick() { if (mainView != null) { mainView.showProgress(); } getQuoteInteractor.getNextQuote(this); } @Override public void onDestroy() { mainView = null; } @Override public void onFinished(String string) { if (mainView != null) { mainView.setQuote(string); mainView.hideProgress(); } } } |
The above class implements the Presenter and nested interface from GetQuoteInteractor. Moreover it instantiates the MainView
and GetQuoteInteractor
interfaces (View and Model respectively).
onButtonClick
method would be triggered in the MainActivity class when the button is clicked and will display a progressBar
while it gets the next random quote.
onDestroy()
method would be invoked inside the lifecycle method onDestroy() of the MainActivity.
onFinished()
method gets called when the handler is completed inside the GetQuoteInteractorImpl. It returns the string which will be displayed in the TextView using the MainView’s instance.
The code for the MainActivity.java
which implements MainView interface is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package com.journaldev.hellomvp; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import static android.view.View.GONE; public class MainActivity extends AppCompatActivity implements MainView { private TextView textView; private Button button; private ProgressBar progressBar; MainPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); button = (Button) findViewById(R.id.button); progressBar = (ProgressBar) findViewById(R.id.progressBar); presenter = new MainPresenterImpl(this, new GetQuoteInteractorImpl()); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.onButtonClick(); } }); } @Override protected void onResume() { super.onResume(); } @Override protected void onDestroy() { super.onDestroy(); presenter.onDestroy(); } @Override public void showProgress() { progressBar.setVisibility(View.VISIBLE); textView.setVisibility(View.INVISIBLE); } @Override public void hideProgress() { progressBar.setVisibility(GONE); textView.setVisibility(View.VISIBLE); } @Override public void setQuote(String string) { textView.setText(string); } } |
showProgress()
and hideProgress()
methods shows and hides the textView as well to prevent overlapping the current text and the progressBar
.
Note: The above class contains is now devoid of the business logic and is responsible for only updating the UI based on the changes triggered by the Presenter layer.
The output of the above android MVP application is given below.
Note: Google recommends to keep a single contract interface file for the Model View and Presenter.
So let’s club the interfaces defined above into one.
The project structure now looks like this:
The code for the MainContract.java
interface is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.journaldev.hellomvp; public interface MainContract { interface MainView { void showProgress(); void hideProgress(); void setQuote(String string); } interface GetQuoteInteractor { interface OnFinishedListener { void onFinished(String string); } void getNextQuote(OnFinishedListener onFinishedListener); } interface Presenter { void onButtonClick(); void onDestroy(); } } |
This brings an end to this tutorial. There’s a lot more to explore in MVP pattern that we’ll be discussing soon.
You can download the Android MVP Hello World Project from the link below.
Reference: Wikipedia