MVP

~~Deprecated~~

The Android team has moved past MVP architecture and is now using MVVM on all new projects. We are keeping this chapter since there is still a large number of projects that use MVP structure, and we are keeping this as reference. If you are starting a new project or refactoring the existing one, check the MVVM architecture setup described in the Project architecture chapter.

MVP is short for Model-View-Presenter, and it is a derivative from the MVC (Model View Controller) design pattern. The main difference between the two is shown in the picture below.

MVC vs MVP

What is MVP?

The MVP pattern is a way to separate background tasks from Activities/Fragments/Views to make the application simpler, code shorter, and maintainability better. MVP does all this by splitting the logic into three layers:

Why use MVP?

The main reason to use the MVP pattern is the KISS principle. Without using MVP, your Activity contains UI, UI logic, and data management, which means lots of lines of code that are hard to maintain and cannot be reused. MVP splits complex tasks into multiple simpler tasks so they becomes easier to solve, and classes have less code, which means they become much easier for your colleagues to maintain, debug and understand. MVP also allows unit testing because you can individually test each component of the app by recreating real use cases with mock data.

Model

The Model is a representation of business logic, or to put it simpler: data that will be displayed in the user interface (view). It is implemented by interactors that are responsible for some specific action (fetching data from the API, reading from a file...). After that, the result is sent to the presenter via listener.

View

The view is an interface that displays data (model) and is responsible for handling user input and invoking the corresponding method of the presenter. The view does only what the presenter tells it to do, and it shouldn't contain any logic.

Presenter

The presenter is the “middle-man” and has references to the view and model. For instance, if your app needs to display the data of a user, the presenter would use its reference to a database business logic, from where it would query a list of users and find the one it needs. Then, it would call a method in the view, passing the queried user, and the view would update the UI with the user data.

Listener

The listener is an interface implemented inside the presenter and is used for retrieving data from the model. As the model is usually asynchronous, the presenter needs a way to listen when data is successfully fetched or when some kind of error happens. The listener is passed to the interactor as a method parameter, and it is not injected by Dagger like other components.

How to implement MVP?

Initial packages

public interface LoginView {

    void hideProgress();

    void showProgress();

    void showError(String message);
}
public interface LoginPresenter {

    void onLoginClick(String username, String password);
}
public interface LoginInteractor {

    void login(LoginListener listener, String username, String password);
}
public interface LoginListener {

    void onLoginSuccess (String message);

    void onLoginFail (String message);
}

MVP final packages

public class LoginActivity extends AppCompatActivity implements LoginView {

    Button loginButton;
    EditText usernameEditText;
    EditText passwordEditText;
    ProgressDialog progressDialog;

    @Inject
    LoginPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginButton = (Button) findViewById(R.id.button);
        loginButton.setOnClickListener(clickListener);
        initializeView();
    }

    private void initializeView() {
        usernameEditText = (EditText) findViewById(R.id.username);
        passwordEditText = (EditText) findViewById(R.id.password);
        DaggerLoginComponent.builder()
                .loginModule(new LoginModule(this))
                .build()
                .inject(this);
    }

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String username = usernameEditText.getText().toString();
            String password = passwordEditText.getText().toString();
            mPresenter.onLoginClick(username, password);
        }
    };

    @Override
    public void hideProgress() {
        progressDialog.dismiss();
    }

    @Override
    public void showProgress() {
        if (progressDialog == null) {
            progressDialog = ProgressDialog
                    .show(this, "Loading", "Please Wait", true, false);
        } else {
            progressDialog.show();
        }
    }

    @Override
    public void showError(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void navigateToSplash() {
        Intent intent = new Intent(this, SplashActivity.class);
        startActivity(intent);
        finish();
    }
}
public class LoginPresenterImpl implements LoginPresenter {

    private LoginView loginView;
    private LoginInteractor loginInteractor;

    @Inject
    public LoginPresenterImpl(LoginView loginView, LoginInteractor loginInteractor) {
        this.loginView = loginView;
        this.loginInteractor = loginInteractor;
    }

    @Override
    public void onLoginClick(String username, String password) {
        loginView.showProgress();
        loginInteractor.login(listener, username, password);
    }

    LoginListener listener = new LoginListener() {
        @Override
        public void onLoginSuccess(String message) {
            loginView.hideProgress();
            loginView.navigateToSplash();
        }

        @Override
        public void onLoginFail(String message) {
            loginView.hideProgress();
            loginView.showError(message);
        }
    };
}
public class LoginInteractorImpl implements LoginInteractor {

    LoginListener listener;

    @Inject
    public LoginInteractorImpl() {
    }

    @Override
    public void login(LoginListener listener, String username, String password) {
        this.listener = listener;
        startDummyLogin();
    }

    private void startDummyLogin() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                listener.onLoginSuccess("Success");
            }
        }, 4000);
    }
}

Useful links