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
ActivityorFragment, 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();
}
}