Architecture Patterns (MVC-MVP-MVVM)

With Android/Kotlin sample code.

2023-02-09 19:17:01 - Mohamad Abuzaid


Note: In this article I am using Android/Kotlin for reference and sample code. However, the concept behind these architecture patterns remains the same for all programming languages or frameworks.

In software development, architecture patterns are general solutions to common problems that occur when designing software systems. These patterns provide a way to organize and structure the code, making it more maintainable, testable, and scalable. Examples of architecture patterns include the Model-View-Controller (MVC) pattern, the Model-View-Presenter (MVP) pattern and the Model-View-ViewModel (MVVM) pattern. These patterns help developers to build and maintain large and complex software systems by providing a clear and consistent way to organize the code.

--------------------

Model-View-Controller (MVC)

The (MVC) pattern is a way to separate the code for the user interface (UI) from the code that handles the business logic and data.

The Model represents the data and the business logic of the application. It holds the state of the application and handles any updates to that state. The Model is typically implemented as a set of classes or objects that represent the data and the business logic.

class TaskModel {
    var taskList = mutableListOf<String>()

    fun addTask(task: String) {
        taskList.add(task)
    }

    fun removeTask(index: Int) {
        taskList.removeAt(index)
    }
}


The View represents the UI of the application. It is responsible for displaying the data from the Model and handling any user interactions. In Android, the View is typically implemented as an XML layout file and a corresponding Java class.

The Controller is responsible for coordinating the Model and the View. It receives user input from the View and updates the Model accordingly. In Android, the Controller is typically implemented as the Activity or Fragment class.

class MainActivity : AppCompatActivity() {

    lateinit var adapter: ArrayAdapter<String>
    lateinit var taskModel: TaskModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        taskModel = TaskModel()

        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, taskModel.taskList)
        taskListView.adapter = adapter

        addButton.setOnClickListener {
            val task = taskEditText.text.toString()
            taskModel.addTask(task)
            adapter.notifyDataSetChanged()
        }

        taskListView.setOnItemClickListener { _, _, position, _ ->
            taskModel.removeTask(position)
            adapter.notifyDataSetChanged()
        }
    }
}


The Model TaskModel contains the state of the application which is a list of tasks, and it has methods to add and remove tasks from the list. The View MainActivity is responsible for displaying the data from the Model and handling any user interactions. It uses an ArrayAdapter to display the list of tasks, and it sets up listeners for the add and remove buttons.

MVC pattern helps to separate the concerns, making it easier to test and maintain the code. The Model does not depend on the View, so it can be tested separately. The View does not depend on the Model, so it can be easily replaced. The Controller is the glue that holds the Model and the View together, so it can be easily modified to change the behavior of the application.

Note: In Android, the Activity acts as the Controller, coordinating the Model and the View by updating the Model when the user interacts with the UI and updating the View when the Model changes. That is why some MVC implementations in Android might not have clear separation of concerns, making the code harder to understand and maintain.


Pros & Cons

The Model-View-Controller (MVC) pattern has several advantages and disadvantages:

Pros:

Cons:

--------------------

Model-View-Presenter (MVP)

The Model-View-Presenter (MVP) pattern is a variation of the Model-View-Controller (MVC) pattern that is designed to improve the maintainability and testability of the code.

The Model represents the data and the business logic of the application, similar to MVC. It holds the state of the application and handles any updates to that state. The Model is typically implemented as a set of classes or objects that represent the data and the business logic.

class TaskModel {
    var taskList = mutableListOf<String>()

    fun addTask(task: String) {
        taskList.add(task)
    }

    fun removeTask(index: Int) {
        taskList.removeAt(index)
    }
}


The View represents the UI of the application, similar to MVC. It is responsible for displaying the data from the Model and handling any user interactions. In Android, the View is typically implemented as an XML layout file and a corresponding Java or Kotlin class.

interface TaskView {
    fun updateTaskList(taskList: List<String>)
    fun showError(message: String)
}

class MainActivity : AppCompatActivity(), TaskView {

    lateinit var presenter: TaskPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val taskModel = TaskModel()
        presenter = TaskPresenter(this, taskModel)

        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, taskModel.taskList)
        taskListView.adapter = adapter

        addButton.setOnClickListener {
            val task = taskEditText.text.toString()
            presenter.addTask(task)
        }

        taskListView.setOnItemClickListener { _, _, position, _ ->
            presenter.removeTask(position)
        }
    }

    override fun updateTaskList(taskList: List<String>) {
        adapter.clear()
        adapter.addAll(taskList)
        adapter.notifyDataSetChanged()
    }

    override fun showError(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}


The Presenter acts as the middle man between the Model and the View. It receives user input from the View, updates the Model accordingly, and tells the View to update itself based on the new Model data. The Presenter also reacts to changes in the Model by updating the View. In MVP pattern the Presenter is responsible for handling the logic that glues the Model and the View together.

class TaskPresenter(private val view: TaskView, private val model: TaskModel) {
    fun addTask(task: String) {
        if (task.isEmpty()) {
            view.showError("Task cannot be empty.")
        } else {
            model.addTask(task)
            view.updateTaskList(model.taskList)
        }
    }

    fun removeTask(index: Int) {
        model.removeTask(index)
        view.updateTaskList(model.taskList)
    }
}


The Model TaskModel contains the state of the application which is a list of tasks, and it has methods to add and remove tasks from the list. The View MainActivity is responsible for displaying the data from the Model and handling any user interactions. It uses an ArrayAdapter to display the list of tasks, and it sets up listeners for the add and remove buttons. The Presenter TaskPresenter acts as the middle man between the Model and the View, it receives user input from the View, updates the Model accordingly, and tells the View to update itself based on the new Model data. The View communicates with the Presenter through an interface TaskView that defines the methods that the Presenter can call on the View.

Similar to (MVC), MVP pattern helps to separate the concerns, making it easier to test and maintain the code. The Model does not depend on the View, so it can be tested separately. The View does not depend on the Model, so it can be easily replaced. The Presenter is the glue that holds the Model and the View together, so it can be easily modified to change the behavior of the application.


Pros & Cons

The Model-View-Presenter (MVP) pattern has several advantages and disadvantages when compared to the Model-View-Controller (MVC) pattern:

Pros:

Cons:

-------------------

Model-View-ViewModel (MVVM)

The Model-View-ViewModel (MVVM) pattern is a variation of the Model-View-Controller (MVC) pattern that is designed to improve the maintainability and testability of the code. It’s heavily inspired by the MVP pattern and it’s based on the separation of concerns principle.

The Model represents the data and the business logic of the application, similar to MVC and MVP. It holds the state of the application and handles any updates to that state. The Model is typically implemented as a set of classes or objects that represent the data and the business logic.

class TaskModel {
    var taskList = mutableListOf<String>()

    fun addTask(task: String) {
        taskList.add(task)
    }

    fun removeTask(index: Int) {
        taskList.removeAt(index)
    }
}


The View represents the UI of the application, similar to MVC and MVP. It is responsible for displaying the data from the Model and handling any user interactions. In Android, the View is typically implemented as an XML layout file and a corresponding Java or Kotlin class.

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding
    lateinit var viewModel: TaskViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val taskModel = TaskModel()
        viewModel = TaskViewModel(taskModel)
        binding.viewModel = viewModel

        binding.addButton.setOnClickListener {
            val task = binding.taskEditText.text.toString()
            viewModel.addTask(task)
        }

        binding.taskListView.setOnItemClickListener { _, _, position, _ ->
            viewModel.removeTask(position)
        }
    }
}


The ViewModel is a new component in MVVM. It’s a non-UI component that acts as a bridge between the Model and the View. It’s responsible for exposing the data from the Model in a format that’s appropriate for the View to consume, and it also handles the logic for the View. It’s also responsible for handling the state of the UI and any commands that are issued by the View.

class TaskViewModel(private val model: TaskModel) {
    val taskList = ObservableArrayList<String>().apply {
        addAll(model.taskList)
    }

    fun addTask(task: String) {
        if (task.isEmpty()) {
            // Show error message
        } else {
            model.addTask(task)
            taskList.add(task)
        }
    }

    fun removeTask(index: Int) {
        model.removeTask(index)
        taskList.removeAt(index)
    }
}


The Model TaskModel contains the state of the application which is a list of tasks, and it has methods to add and remove tasks from the list. The View MainActivity is responsible for displaying the data from the Model and handling any user interactions. It uses Data binding to bind the View to the ViewModel, and it sets up listeners for the add and remove buttons. The ViewModel TaskViewModel acts as a bridge between the Model and the View, it receives user input from the View, updates the Model accordingly, and updates the View based on the new Model data. It’s also responsible for handling the state of the UI and any commands that are issued by the View. The ViewModel exposes the data from the Model in a format that’s appropriate for the View to consume, it also handles the logic for the View.

Again, similar to the previous (MVC) and (MVP), MVVM pattern helps to separate the concerns, making it easier to test and maintain the code. The Model does not depend on the View, so it can be tested separately. The View does not depend on the Model, so it can be easily replaced. The ViewModel is the glue that holds the Model and the View together, so it can be easily modified to change the behavior of the application.Model-View-ViewModel (MVVM).


Pros & Cons

The Model-View-ViewModel (MVVM) pattern has several advantages and disadvantages when compared to the Model-View-Controller (MVC) and Model-View-Presenter (MVP) pattern:

Pros:

Cons:

-----------------

How To choose the best Architecture?


Choosing the best architecture for your project depends on several factors, including the size and complexity of the project, the development team’s experience and expertise, and the project’s specific requirements and constraints. Here are some general guidelines to help you choose the best architecture for your project:


  1. Understand the problem: Before choosing an architecture, it’s important to understand the problem that your project is trying to solve, and to have a clear understanding of the requirements and constraints of the project.
  2. Consider the size and complexity of the project: For small and simple projects, a simple architecture such as MVC might be sufficient. For larger and more complex projects, a more robust architecture such as MVP or MVVM might be more appropriate.
  3. Consider the development team’s experience and expertise: If the development team is experienced with a certain architecture, it might be wise to stick with that architecture, as it will make the development process more efficient and reduce the risk of introducing bugs.
  4. Consider the project’s scalability: The architecture you choose should be scalable and should be able to handle the growth and changing requirements of the project.
  5. Consider the testability: The architecture you choose should make it easy to write automated unit tests and should make it easy to test the different parts of the project in isolation.
  6. Consider the maintainability: The architecture you choose should make it easy to maintain the codebase over time and should make it easy to add new features and fix bugs.
  7. Consider the community and resources: The architecture you choose should have a strong community and a good number of resources, such as tutorials, forums, and libraries, available.


Overall, the best architecture for your project will depend on the specific needs and constraints of your project. Additionally, it’s not always necessary to choose a specific architecture, you can always adopt certain principles or elements of different architectures to suit your needs. It’s always a good idea to evaluate your project’s requirements and constraints regularly, and make adjustments to your architecture as necessary.

More Posts