ZOFTINO.COM android and web dev tutorials

Android Model View Presenter MVP Pattern Example

If you want to build android apps which can be tested and modified easily, you need to implement user interface architectural pattern in your app. User interface architectural patterns separates view components from code related to data.

One of the user interface architectural patterns is Model View Presenter (MVP) which is widely used in Android apps to separate presentation components from data provider.

In this post, you can find information about MVP concepts and how to implement MVP in android app with an example.

Table of Contents

What is MVP

MVP pattern separates model from view using presenter. With this separation, app components can be tested and modified independently (for example replacing data source), designers or developers can independently work on their respective components and components can be reused within app or across applications of an organization.

model view presenter user interface architectural patter

In MVP pattern, model is the data provider. View passes the user interface events to the presenter for results and displays the data received from model via presenter.

Presenter interacts with both model and view. It is a mediator between view and model. It receives the user events from view, interacts with model to get results and updates the view with the results.

In android applications, view is the activity or fragment. Model is the repository classes which provide data from local or remote data source. Presenter is a class which holds reference to view and model and interacts with them.

How to Implement MVP in Android App

In Android apps, a screen or view consists of an activity or a fragment and their layouts. Layout defines UI components of user interface and activity or fragment handles UI events and populates results in UI components. So, view part of MVP is activity or fragment and its layout.

In apps, each UI event results in execution of certain behavior and return of result. The component which provides behavior and results is called model.

If an activity or a fragment directly calls repository or data source classes in response to UI events to get results, testing and modification of app will be difficult. This is where the presenter comes into picture. It separates view and model by mediating between them.

model view presenter mvp in android implementation

To implement MVP, the first thing that needs to be done is to decide what can be done in view and what should be delegated to presenter which intern interacts with model. Depending on your app and type organization, you can decide the degree of app logic that views in your app contains.

In MVP, view contains presenter and passes events to it. Presenter, which contains both view and model, interacts with model to get data and updates the view with results.

As shown in the diagram above, create view interface with methods that presenters calls, create presenter interface with the methods that view calls and inject the instance of presenter to view & instance of view to presenter. Inject instance of model to presenter. See below example for detailed explanation of calls between view, presenter, and model.

Android MVP Example

Let’s see how MVP can be implemented in android using donations tracking app example. The app lets user enter and save donation amount and it displays total money donated.

The read and write data events are passed to the presenter which in turn calls model to obtain the results and then updates the results to view.

model view presenter mvp in android example

One important point that needs to be mentioned before we get into the details of the example is that MVP may leak actions if not implemented properly as view holds a reference to presenter. If presenter can’t be garbage collected due to long running tasks, activity can’t be garbage collected leading to memory leak. And also, background thread started when an activity is open tries to update it with results after the activity is gone leading to app crash.

These issues can be prevented by cleaning resources in the activity or fragment lifecycle callback methods such as start, stop, pause, and resume. It is easy to clean background work if RxJava is used. The example uses RxJava and Room.

Activity

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.zoftino.mvpandroidexample.repository.LocalRepository;

public class DonationActivity extends AppCompatActivity implements ViewInterface{
    @NonNull
    private PresenterInterface presenter;
    @NonNull
    private TextView totalAmt;
    @NonNull
    private EditText newDonation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_donation);

        setUp();
    }
    private void setUp(){
        //initialize db
        LocalRepository.initiateDB(getApplication());

        //set views
        setViews();

        //set presenter
        presenter = new Presenter(this);
    }

    public void addNewDonation(View view){
        addDonation(newDonation.getText().toString());
    }

    private void setViews(){
        totalAmt = findViewById(R.id.d_amount);
        newDonation = findViewById(R.id.add_amt);
    }

    @Override
    public void addDonation(String donationAmt) {
        presenter.addDonation(donationAmt);
    }

    @Override
    public void setTotalDonation(double totalDonation) {
        totalAmt.setText(""+totalDonation);
    }
    @Override
    public void displaySaveMessage(String msg){
        Toast.makeText(this, msg, Toast.LENGTH_LONG);
        newDonation.setText("");
        //refresh total donation on the screen
        presenter.getTotalDonation();
    }

    @Override
    protected void onPause() {
        super.onPause();
        //cleanup resources
        presenter.stop();
    }

    @Override
    protected void onResume() {
        super.onResume();
        //add resources
        presenter.start();
    }

}

Presenter

import com.zoftino.mvpandroidexample.repository.Donation;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;

public class Presenter implements PresenterInterface {
    private CompositeDisposable compositeDisposable;
    private ModelInterface model;
    private ViewInterface view;

    public Presenter(ViewInterface view) {
        model = new DonationModel();
        this.view = view;
    }

    @Override
    public void start() {
        compositeDisposable = new CompositeDisposable();
        getTotalDonation();
    }

    @Override
    public void stop() {
        compositeDisposable.clear();
    }

    public void getTotalDonation() {
        compositeDisposable.add(model.getDonation()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::setDonation));
    }

    private void setDonation(Donation donation) {
        if (donation != null)
            view.setTotalDonation(donation.getAmount());
    }

    //add donation
    public void addDonation(String donation) {
        double donationAmt;
        try {
            donationAmt = Double.parseDouble(donation);
        } catch (NumberFormatException exception) {
            sendSaveMessage("Please enter valid donation amount");
            return;
        }
        //add observable to disposable and
        //subscribe to the observable on main thread to send message to view
        compositeDisposable.add(model.saveDonationAmt(donationAmt)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::sendSaveMessage));
    }

    //add donation status message to view
    private void sendSaveMessage(String msg) {
        view.displaySaveMessage(msg);
    }
}

Model

import com.zoftino.mvpandroidexample.repository.Donation;
import com.zoftino.mvpandroidexample.repository.DonationDAO;
import com.zoftino.mvpandroidexample.repository.LocalRepository;

import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;


public class DonationModel implements ModelInterface{
    private DonationDAO donationDAO;

    public DonationModel(){
        donationDAO = LocalRepository.getDonationDAO();
    }

    @Override
    public Maybe<Donation> getDonation() {
        return Maybe.just(1).subscribeOn(Schedulers.computation())
                .flatMap(this::getDonationObj);
    }

    private Maybe<Donation> getDonationObj(int i) {
         return donationDAO.getDonation();
    }
    @Override
    public Donation getDonationObject(){
        return donationDAO.getDonationObject();
    }

    @Override
    public Observable<String> saveDonationAmt(double donationAmt) {
       return Observable.just(donationAmt).subscribeOn(Schedulers.computation())
                .flatMap(this::saveDonation);
    }

    private Observable<String> saveDonation(double donationAmt){
        try {
            Donation donation = getDonationObject();
            if (donation == null) {
                donation = new Donation();
                donation.setId(1);
            }
            donation.setAmount(donation.getAmount() + donationAmt);
            donationDAO.saveDonation(donation);
        }catch (Exception e){
            return Observable.just("Donation couldn't be saved");
        }
        return Observable.just("Donation saved");
    }
}

Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="8dp"
    tools:context=".DonationActivity">

    <TextView
        android:id="@+id/d_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="Total Donations So Far"
        android:textSize="24dp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/d_amount"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/d_amount"
        android:layout_marginTop="24dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="33333"
        android:textSize="24dp"
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="@+id/d_title"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/add_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="Enter new donation amount"
        android:textSize="24dp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/d_title" />

    <EditText
        android:id="@+id/add_amt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:hint="Enter new donation"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/add_title" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="addNewDonation"
        style="@style/Widget.AppCompat.Button.Colored"
        android:text="Add New Donation"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/add_amt" />

</android.support.constraint.ConstraintLayout>