Choosing between these patterns is a constant subject of discussions and fights, and developers’ perceptions of them are different and cause further confusion. Is there any way out of this ideological problem? Let’s talk about MVC, MVP, MVVM, and MVI pragmatically – Let’s answer “Why?” and “What are the consequences?”
My obsession with MVC, MVP, MVVM, and MVI started when I was doing the application for Warta Mobile with my team. We needed to move from a minimally viable product into a tested and fully featured application, and we knew that we had to introduce one of the design patterns.
Many people have strong opinions about different patterns. But when you look at a single pattern, such as the MVC pattern, aside from the constant elements of the Model, View, and Controller, what people described of each are different.
The creator of MVC, Trygve Reenskaug, he initially invented and defined MVC. 24 years after its creation, he described it not as a pattern but as a set of real patterns that were built on the MVC idea.
I came to the conclusion that because every project is different, there is no one perfect architectural pattern.
We should think about parameters and the trade-offs that come with different patterns.
What Do We Want to Achieve?
Scalability, maintainability, reliability
Get more development news like this
You can think about Scalability in terms of if it’s reasonable to continue implementing new features in the future, or if the project needs to be scrapped.
Maintainability can be viewed in the context of small changes that need to be done after features are completed, such as changing a color. APIs change and you may new developers to maintain older projects.
Reliability. We don’t have the patience for the floppy apps.
Separation of Concerns, Code Reusability, Testability
The most important element here is the separation of concerns: different ideas should be separated. If we would want to change something, we should not have to go into different areas of code.
Additionally, both Code Reusability and Testability relies on separations of concerns, as these would be difficult to achieve without them.
Independence, as noted by Uncle Bob in Clean Architecture is key. For example, if you are using a library for image loading, you do not want to marry it with another library to solve a problem caused by it! Supporting independence in the architectural pattern will also support the scalability and maintainability.
MVC Supervising Controller Passive View
Our MVC has two main variants: supervising controller and passive view.
In the mobile ecosystem, I have never seen the supervising controller implemented. From the historical point of view it is important because it’s connected to the initial definition of MVP, especially because of two fragments:
- One is that the view is a visual representation the of model.
- And the controller is a link between the user and the system.
This is an ideological diagram, it is impossible to implement in this manner, as the view is defining both listeners and callbacks; the view is passing input to the controller.
The controller should take the input, and the viewer should take the output, but most of the operations are in between. This model is good for smaller projects.
MVC Passive View
The general idea with the MVC Passive View is that the view is stateless, and is fully controlled by the controller. More specially, we are separating the two different and well-separated concerns as business and presentation logic.
- Business logic is how our application is acting.
- The presentation logic is how our application looks like.
Massive View Controller
We should not treat an Activity as a view. We should treat it as a presentation layer and we should extract our controller as a separate class.
A solution to making view controllers smaller is by splitting views or defining subviews with their own controllers. Writing the MVC pattern this way is easy to separate, and it is also easy to split. But, there are issues here with my implementation.
- Mixing presentation and business logic - here, I’ve passed elements of the view into the controllers.
- Doing this makes it more difficult to test.
It’s possible to solve these issues by doing one thing - to hide the view behind the interface behind the abstraction. Now, the presenter depends only on this abstraction. Our tests are easier and all these problems are solved. This is the general idea behind MVP.
Model View Presenter
We can easily make our unit test without using a library. The presenter in MVP is highly testable and is very reusable because our view can implement more interfaces, presenters can use common modules.
In terms of how to specifically create and maintain the interfaces, you should view MVP and MVC as only general ideas instead of patterns.
Creating Use cases is the process of extracting separate business rules into separate classes, making them part of the model. They are independent of the controller and each consist of one business rule. This helps with reusability and makes them highly testable.
In our example, a login controller, I extracted the validation use case and the login use case. Our login use case is making network operation. If we have common business rules in different controllers or presenters, then we can reuse our use cases.
In implementing MVPs, there are four line functions that do nothing but to make small changes in the layout. To avoid this extra code, you can use view binding.
All the binding can be found in my library Kotlin Android view bindings.
This is a simple approach: it’s easy to test, and it’s easier to represent the elements of the view as the properties on the interface instead of as a function.
From our presenter point of view, almost nothing has changed.
Another option is instead of binding the view into the interface fields, we bind the elements of the view into the properties of the view model - this is MVVM. In our example implementation, I made the fields, e.g. email and password, and in the layout defined by the bindings. When we are changing the properties in our review model, it is triggering changes in the layout.
It is easy to test such view models because you don’t need to make any mock objects - you are changing your own elements and checking how they changed.
There is one more element that you can switch in our actual architecture, commonly called as the MVI. If we take our layout elements (e.g. button), from our perspective they are nothing but the producers of data.
In the library, these are called an observable, something that is producing events. In a TextView, it only accepts/receives texts. In RxJava Library this is called a consumer.
There are elements that are both producing and receiving data, such as a TextEdit where a user can set the text as well. These are both producer and receiver, they are called subject in RxJava. In this case, everything is a stream and all the streams are started from some producer going through some processing and finishing on some consumer. As a result, you can start viewing your application as a big data flow. Certain data flow is clear and it is beneficial if you like Rx interactive approach.
Separating concerns can improve a codebase overall, making it more scalable, attainable, and reliable. But it can be more more difficult to create and maintain. As a solution, you can extract the interface to allow for easier testing, and code can be reused.
Additional patterns like MVVM and remove some of the boilerplate, and MVI can even further improve scalability, but going this route will make your app dependent on the RxJava Library.
But, you can pick and choose these elements, as part of configuring your app depending on your needs.
You can find all code used in this presentation here.
About the content
This talk was delivered live in October 2017 at Mobilization. The video was transcribed by Realm and is published here with the permission of the conference organizers and speakers.