MVC vs MVP vs MVVM architecture patterns

Architecture Patterns:

MVC (Model — View — Controller), MVP (Model — View — Presenter), and MVVM (Model — View — ViewModel) is the popular architectures patterns in software development. The basic idea of using these architectures patterns is to separate the business logic and UI part. By doing this, your code becomes to be more readable and testable. Let’s understand these patterns and how we can use that in the actual project.

The Model—View—Controller(MVC) Pattern

As the name suggests, MVP contains a total of 3 components. Model, View, and Presenter. You can use the MVP architecture to overcome the limitations of MVC architecture. For example, in MVC, code becomes more tightly coupled, which reduces the code’s testability and makes it harder to refactor and change.

MVC (Model-View-Controller):

  • Model: Represents the data and business logic.
  • View: Represents the UI components.
  • Controller: Acts as an intermediary between Model and View, handling user input and updating the model accordingly.

Example: Let’s create a simple login screen.

// Model
public class UserModel {
    private String username;
    private String password;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isValid() {
        // Check if username and password are valid
        return !TextUtils.isEmpty(username) && !TextUtils.isEmpty(password);
    }
}

// View
public class LoginActivity extends AppCompatActivity {
    private EditText usernameEditText;
    private EditText passwordEditText;
    private Button loginButton;

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

        usernameEditText = findViewById(R.id.usernameEditText);
        passwordEditText = findViewById(R.id.passwordEditText);
        loginButton = findViewById(R.id.loginButton);

        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Handle login button click
                String username = usernameEditText.getText().toString();
                String password = passwordEditText.getText().toString();

                // Pass user input to the Controller
                UserController.getInstance().login(username, password);
            }
        });
    }
}

// Controller
public class UserController {
    private static final UserController instance = new UserController();

    private UserController() {}

    public static UserController getInstance() {
        return instance;
    }

    public void login(String username, String password) {
        UserModel userModel = new UserModel();
        userModel.setUsername(username);
        userModel.setPassword(password);

        // Check if credentials are valid
        if (userModel.isValid()) {
            // Navigate to the next screen
            // Example: startActivity(new Intent(LoginActivity.this, HomeActivity.class));
        } else {
            // Display error message
            Toast.makeText(MyApplication.getInstance(), "Invalid username or password", Toast.LENGTH_SHORT).show();
        }
    }
}

Advantages:

  • Simplicity: Easy to understand and implement, especially for smaller projects.
  • Familiarity: Many developers are already familiar with MVC from traditional desktop development.

Disadvantages:

  • Tight coupling: Views often directly interact with Controllers, leading to tight coupling and making it harder to test and maintain.
  • Massive View Controllers: As the app grows, the Controllers can become bloated with too much logic, leading to maintenance issues.

The Model—View—Presenter(MVP) Pattern

As the name suggests, MVP contains a total of 3 components. Model, View, and Presenter. You can use the MVP architecture to overcome the limitations of MVC architecture. For example, in MVC, code becomes more tightly coupled, which reduces the code’s testability and makes it harder to refactor and change.

  • Model: Represents the data and business logic.
  • View: Represents the UI components.
  • Presenter: Acts as an intermediary between Model and View, handling user input and updating the model. Unlike MVC, the Presenter manipulates the View directly.

Example: Using the same login screen example:

// Model remains the same as in MVC example

// View
public interface LoginView {
    void showInvalidCredentialsError();
    void navigateToHome();
}

public class LoginActivity extends AppCompatActivity implements LoginView {
    private EditText usernameEditText;
    private EditText passwordEditText;
    private Button loginButton;
    private LoginPresenter presenter;

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

        usernameEditText = findViewById(R.id.usernameEditText);
        passwordEditText = findViewById(R.id.passwordEditText);
        loginButton = findViewById(R.id.loginButton);

        presenter = new LoginPresenter(this);

        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.onLoginClicked(usernameEditText.getText().toString(), passwordEditText.getText().toString());
            }
        });
    }

    @Override
    public void showInvalidCredentialsError() {
        Toast.makeText(this, "Invalid username or password", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void navigateToHome() {
        // Navigate to home activity
    }
}

// Presenter
public class LoginPresenter {
    private LoginView view;

    public LoginPresenter(LoginView view) {
        this.view = view;
    }

    public void onLoginClicked(String username, String password) {
        UserModel userModel = new UserModel();
        userModel.setUsername(username);
        userModel.setPassword(password);

        if (userModel.isValid()) {
            view.navigateToHome();
        } else {
            view.showInvalidCredentialsError();
        }
    }
}

Advantages:

  • Separation of concerns: The Presenter separates the business logic from the View, making it easier to test and maintain.
  • Testability: Presenters can be easily tested without the Android framework, which improves testability.

Disadvantages:

  • Boilerplate code: MVP often requires writing a lot of boilerplate code to manage the communication between the View and the Presenter.
  • Complex UI updates: Presenters might need to handle complex UI updates, leading to increased complexity in the Presenter layer.

Model — View — ViewModel (MVVM) Pattern

As the name suggests, MVP contains a total of 3 components. Model, View, and ViewModel. Like many other architectures patterns, MVVM helps organize code and break programs into modules to make code development, updating, and reuse simpler and faster. MVVM uses the reactive programming model for lesser code.

  • Model: Represents the data and business logic.
  • View: Represents the UI components.
  • ViewModel: Sits between the View and the Model, exposing streams of data relevant to the View and handling UI-specific logic.

Example: For MVVM, let’s continue with the login screen example:

// Model remains the same as in MVC and MVP examples

// View
public class LoginActivity extends AppCompatActivity {
    private EditText usernameEditText;
    private EditText passwordEditText;
    private Button loginButton;
    private LoginViewModel viewModel;

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

        usernameEditText = findViewById(R.id.usernameEditText);
        passwordEditText = findViewById(R.id.passwordEditText);
        loginButton = findViewById(R.id.loginButton);

        viewModel = ViewModelProviders.of(this).get(LoginViewModel.class);

        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewModel.login(usernameEditText.getText().toString(), passwordEditText.getText().toString());
            }
        });

        viewModel.getLoginResult().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean isValid) {
                if (isValid) {
                    // Navigate to home activity
                } else {
                    Toast.makeText(LoginActivity.this, "Invalid username or password", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

// ViewModel
public class LoginViewModel extends ViewModel {
    private MutableLiveData<Boolean> loginResult = new MutableLiveData<>();
    
    public LiveData<Boolean> getLoginResult() {
        return loginResult;
    }

    public void login(String username, String password) {
        UserModel userModel = new UserModel();
        userModel.setUsername(username);
        userModel.setPassword(password);

        // Update login result based on validation
        loginResult.setValue(userModel.isValid());
    }
}

Advantages:

  • Separation of concerns: MVVM separates the business logic from the View, making it easier to test and maintain.
  • Data-binding: MVVM frameworks (like LiveData in Android) enable automatic updates to the View when the ViewModel’s state changes, reducing boilerplate code.
  • Testability: ViewModels are easier to test compared to Presenters, as they are free of Android dependencies.

Disadvantages:

  • Learning curve: MVVM, especially with data-binding frameworks, can have a steep learning curve for developers who are new to reactive programming concepts.
  • Over-reliance on data-binding can sometimes lead to performance issues, especially in complex layouts.

These examples provide a basic understanding of how each architectural pattern can be implemented in Android development, along with their advantages and disadvantages.

Here’s a comparison of MVC, MVP, and MVVM architectural patterns in Android development presented in a tabular format:

AspectMVCMVPMVVM
MeaningModel-View-ControllerModel-View-PresenterModel-View-ViewModel
Separation of ConcernsLoosely separates data, UI, and logicSeparates data (Model), UI (View), and logic (Presenter)Separates data (Model), UI (View), and UI logic (ViewModel)
View DependencyDirectly interacts with ControllerPassive, no direct interaction with PresenterPassive, no direct interaction with ViewModel
Controller/PresenterManipulates both Model and ViewManipulates View, updates Model indirectlyManipulates View, updates Model indirectly
TestabilityCan be challenging due to tight couplingImproved testability, Presenter can be tested in isolationEasier to test, ViewModel is free of Android dependencies
Boilerplate CodeMay lead to bloated View components (Activities/Fragments)Requires boilerplate for communication between View and PresenterData-binding reduces boilerplate for UI updates
Data BindingNot inherentNot inherentSupports data-binding frameworks (e.g., LiveData, RxJava) for automatic UI updates
PopularityTraditional, but declining in favor of MVP/MVVMWidely adopted, especially for Android developmentIncreasing popularity, endorsed by Google for Android development

This table provides a concise comparison of the key aspects of MVC, MVP, and MVVM patterns in Android development, highlighting their differences in terms of separation of concerns, testability, code organization, and popularity within the Android community.