Implementing the Model-View-Presenter Pattern for Android Login Functionality

Context and Motivation

Many developers are familiar with MVP (Model-View-Presenter) as an evolution of MVC, offering clearer separation of concerns, particularly for decoupling the Model from the View. This architectural pattern has gained widespread acceptance among Android developers primarily because it structures code more clearly, even though it may increase the number of classes.

The standard interpretation of MVC on Android often conflates roles:

  • View: Corresponds to layout files.
  • Model: Encompasses business logic and data entities.
  • Controller: Often equated with the Activity.

In practice, this leads to the Activity handling both UI updates (View) and user input processing (Controller), resulting in a bloated class. MVP addresses this by introducing a dedicated Presenter:

  • View: The Activity or Fragment, responsible for UI rendering and user interaction.
  • Model: Manages business logic and data entities.
  • Presenter: Acts as an intermediary, handling all interaction logic between the View and Model. This separation reduces the Activity's responsibilities, promotes testability, and lowers coupling.

Distinciton Between MVP and MVC

The primary distinction is in the communication flow. In traditional MVC, the Model and View can interact directly. In MVP, all communication between the Model and View is routed through the Presenter. Furthermore, the View and Presenter communicate via defined interfaces, not direct references.

Example: Building a Login Screen

This example demonstrates the implementation for a basic login screen.

1. Defining the Model Layer

The Model layer includes the data entity and the business logic contract.

User.java - Data Entity

public class UserAccount {
    private String userName;
    private String passKey;

    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }

    public String getPassKey() { return passKey; }
    public void setPassKey(String passKey) { this.passKey = passKey; }
}

AuthenticationContract.java - Business Logic Interface

public interface AuthenticationContract {
    void authenticate(String userName, String passKey, AuthCallback callback);
}

AuthCallback.java - Operation Callback

public interface AuthCallback {
    void onAuthenticationSuccess(UserAccount account);
    void onAuthenticationFailure();
}

AuthenticationHandler.java - Business Logic Implementation

public class AuthenticationHandler implements AuthenticationContract {
    @Override
    public void authenticate(final String userName, final String passKey, final AuthCallback callback) {
        new Thread(() -> {
            try {
                Thread.sleep(2000); // Simulate network delay
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Mock validation
            if ("testUser".equals(userName) && "password123".equals(passKey)) {
                UserAccount account = new UserAccount();
                account.setUserName(userName);
                account.setPassKey(passKey);
                callback.onAuthenticationSuccess(account);
            } else {
                callback.onAuthenticationFailure();
            }
        }).start();
    }
}

2. Defining the View Layer Interface

The View interface defines the contract for UI interactions. Its implemented by the Activity.

LoginViewContract.java

public interface LoginViewContract {
    // Methods to retrieve user input
    String getInputUserName();
    String getInputPassKey();

    // Methods for UI actions
    void resetUserNameField();
    void resetPassKeyField();

    // Methods for feedback/loading states
    void displayProgressIndicator();
    void hideProgressIndicator();

    // Methods for operation results
    void navigateToHomeScreen(UserAccount account);
    void displayAuthErrorMessage();
}

3. Implementing the View (Activity)

The Activity implements the LoginViewContract interface, providing concrete UI implementations.

LoginActivity.java

public class LoginActivity extends AppCompatActivity implements LoginViewContract {
    private EditText usernameEditText, passwordEditText;
    private Button loginButton, resetButton;
    private ProgressBar loadingProgressBar;
    private LoginPresenter loginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginPresenter = new LoginPresenter(this);
        initializeViews();
    }

    private void initializeViews() {
        usernameEditText = findViewById(R.id.username_edittext);
        passwordEditText = findViewById(R.id.password_edittext);
        loginButton = findViewById(R.id.login_button);
        resetButton = findViewById(R.id.reset_button);
        loadingProgressBar = findViewById(R.id.loading_progressbar);

        loginButton.setOnClickListener(v -> loginPresenter.performLogin());
        resetButton.setOnClickListener(v -> loginPresenter.resetFields());
    }

    // --- Implementation of LoginViewContract ---
    @Override
    public String getInputUserName() {
        return usernameEditText.getText().toString();
    }
    @Override
    public String getInputPassKey() {
        return passwordEditText.getText().toString();
    }
    @Override
    public void resetUserNameField() {
        usernameEditText.setText("");
    }
    @Override
    public void resetPassKeyField() {
        passwordEditText.setText("");
    }
    @Override
    public void displayProgressIndicator() {
        loadingProgressBar.setVisibility(View.VISIBLE);
    }
    @Override
    public void hideProgressIndicator() {
        loadingProgressBar.setVisibility(View.GONE);
    }
    @Override
    public void navigateToHomeScreen(UserAccount account) {
        Toast.makeText(this, "Welcome, " + account.getUserName(), Toast.LENGTH_SHORT).show();
        // Intent to home activity would go here
    }
    @Override
    public void displayAuthErrorMessage() {
        Toast.makeText(this, "Login failed. Please check credentials.", Toast.LENGTH_SHORT).show();
    }
}

4. Implementing the Presenter

The Presenter holds references to the View interface and the Model. It orchestrates the flow: it retrieves data from the View, invokes the Model's business logic, and instructs the View to update based on the results.

LoginPresenter.java

public class LoginPresenter {
    private LoginViewContract view;
    private AuthenticationContract authenticator;
    private Handler uiHandler;

    public LoginPresenter(LoginViewContract view) {
        this.view = view;
        this.authenticator = new AuthenticationHandler();
        this.uiHandler = new Handler(Looper.getMainLooper());
    }

    public void performLogin() {
        view.displayProgressIndicator();
        String name = view.getInputUserName();
        String key = view.getInputPassKey();

        authenticator.authenticate(name, key, new AuthCallback() {
            @Override
            public void onAuthenticationSuccess(final UserAccount account) {
                uiHandler.post(() -> {
                    view.hideProgressIndicator();
                    view.navigateToHomeScreen(account);
                });
            }
            @Override
            public void onAuthenticationFailure() {
                uiHandler.post(() -> {
                    view.hideProgressIndicator();
                    view.displayAuthErrorMessage();
                });
            }
        });
    }

    public void resetFields() {
        view.resetUserNameField();
        view.resetPassKeyField();
    }
}

Tags: Android MVP Architecture Model-View-Presenter login

Posted on Sat, 11 Jul 2026 17:44:25 +0000 by sam06