Disclaimer
Given this topic spans ~50 years, there have been lots of ideas and conflicting interpretations over the time. I’ll summarize what i consider relevant and add some facts along with my impressions. It’s intended as a quick context for developers like me: who arrived after the dust had settled and are left to wonder what happened. I’ll contrast it with Android as thats where i’ve been using these acronyms the most.
Biggest misconception
In Android context I’ve often heard them described as application architectures, this is a wrong idea to have in mind. They are all architectures for handling the UI and its interactions. But they don’t say much about how to organize your business logic and data, thats is still up to you.
MVC
Created in late 70’s by Trygve Reenskaug1 for Xerox PARC, which was an early OS with a GUI. The goal of MVC was to make machines more flexible: to allow information to be interacted with in a manner which the user would find most useful. Think of switching between grid and list views in file manager, or how different document edit layout is from its viewing layout. Views can be different, but they still operate on the same data.
- Model - Likely named after “Mental Model” a user is having of the domain. Technically speaking it contains the business logic and data.
- View - Contains the UI implementation. Observes the data directly from Model. Notifies Controller on user interactions.
- Controller - Receives user interaction from the View and knows how to update the Model accordingly. It does not provide data to View.
Logically the View and Controller are so related that at first they were imagined as a single Editor object. As a refinement, acknowledging their specializations on output and input, it was split to View and Controller. Splitting for testability didn’t seem to be a concern for back then.
Note for correct mental model: Every UI component on screen has its own View-Controller pair! And there exists another View-Controller pair for orchestrating that whole screen.
You can think of MVP, MVVM, MVI as different stricter definitions evolved from the MVC idea.
MVP
Pioneered in the 90’s by Tailgent2 and later Dolphin Smalltalk3. Quick recap:
- Tailgent MVP - Seems like a generalized MVC with a strict definition - a clear framework in which to build any application. Besides replacing Controller with Presenter it adds components such as Selections, Commands and Interactors. Those added components aid the philosophy of having greater separation between the Model and the View.
Why Controller is dropped is perhaps that the Presenter has a much greater role. Controller was only handling user interactions from UI while Presenter orchestrates the whole application. It is the entrypoint that initializes the Views and the Model. - Dolphin Smalltalk MVP - Inspired by Tailgent, but simplified. It doesn’t add those extra components, only MVP. Agrees that Application logic should stay in Presenter instead of Model. To further simplify development the Controller here is allowed to call View directly.
Application logic? - Basically the platform specific logic for you app. Such as a website for a bank, or iOS app for a shop. It is not Business logic (aka Domain Logic), which would describe how the bank or shop itself operates.
Android MVP
On Android testability was a priority. But you can’t really test Presenter in isolation if it depends on a View (Activity). Specially if it can only be instantiated by the Android framework. By applying some Dependency Inversion the View is split into interface and implementation. Thus the Presenter, depending only on the interface, can now be made to interact with mock Views at the test time. The View requiring an interface in my opinion is a key characteristic of this MVP pattern.
Note that giving the Presenter a reference to the View is a common source for memory leaks on Android. Happens when the view reference is not cleared at configuration change.
How to unit test as much relevant UI logic as possible? Move that logic from View to the testable Presenter. Applying the “Humble Object” pattern which makes View passive. That in my opinion is the second key characteristic.
Third major characteristic, again aiding testability and humbling the View even more, is removing the dependency between View and Model. View does not observe Model directly anymore. The Presenter will be the observer that forwards any updates to the View. This is again different from the two older MVP flavors.
MVVM
Introduced around 2005 by Microsoft. The idealized goal was making designers responsible for View instead of developers4. This would mean using visual editors to create Views defined in Xaml.
There are a few more things required to make this work in practice:
- View must be easily able to receive data from ViewModel.
- View must be able to trigger actions by user.
- View must allow some UI logic (if-else conditions)
These lead to, in the same order:
- DataBinding - Allows the View to specify a property known from the ViewModel as source for its components data. DataBinding will make sure to propagate that data and updates to it. No need to write extra code for it.
- Commands pattern - ViewModel defines command objects that encapsulate available user interactions. The Xaml View can now refer to those commands to specify what should happen for example on a Button press. DataBinding again handles all the wiring in the background.
- The View is declarative - designers can specify already in the Xaml the conditional visibility or styling of a component. It would be controlled by referring to a property in the ViewModel.
And this all leads to a very significant characteristic of MVVM. Since ViewModel exposes observable data and commands. And DataBinding handles the communication with the View behind the scenes. The ViewModel does not know anything about the View!
Android MVVM
Android has followed the original idea a lot more closely with MVVM than it did with MVP. We also had our DataBinding, and for Jetpack Compose the declarativity-reactivity is a fundamental principle. For testability, the implication of VM not knowing about View is huge - no need to have mock Views in ViewModel tests anymore!
Biggest difference is perhaps that we do not define strict Command objects for invoking user interactions. Also with the shift to Jetpack Compose UI, we are getting further away from the dream(?) of someone else doing the UI for us.
Also note that strictly speaking, androidx.lifecycle.ViewModel has nothing to do with this pattern. It is just a class which keeps its instance over configuration changes. Sure, its a great base class for MVVM ViewModel if you want your UI state to survive rotation, but it does not require MVVM pattern. You could use MVP on top of the Android ViewModel as well.
ViewModel name: Its literally the MVC Model layer but for the View. Because we dont want to pollute the domain Model with UI logic.
For extra context: imagine a UI where color of a number is based on wether its positive or negative. In MVC a View just observers the Model. To achieve it there you’d need to add the color information to the Model, i.e. the business logic. This is usually very bad decision. But it is okay to add the meaning of the number to Model, such as “good” or “bad”. Then you can map it to red or green or any other color based on its meaning. Sounds nice, doesn’t work! There is still no place where you are allowed to define that mapping in MVC. So the smalltalk devs, already in MVC days came up with a common pattern for solving this5. Solution is to create another Model to serve the Views needs. And as it doesn’t impact the core business logic, its fine to put some color mapping there. And that new Model could be referred to as ViewModel.
MVI
Intoduced by André Staltz around 2015. In my opinion its an evolution of MVVM, making it a bit stricter, thus potentially allowing less defects in large codebases.
It still relies on observability (VM knows nothing of View), but it no longer allows exposing a multiple individual observable states. It specifies a single observable object that represents the whole UI state.
This is interesting as it allows the ViewModel to trivially ensure that UI state it emits is consistent: such as not showing a loader when data is already being shown.
Note there might still be a need for a second state object for ephemeral UI elements, such as a quick popup.
The second aspect MVI specifies how user interactions are invoked on the ViewModel. Instead of defining Commands, or multiple interaction methods in case of Android ViewModel. Here you’ll have a single method on VM that takes the user action - i.e. the Intent6 - as an argument. This implies there will be a single central, perhaps long, method where all state updates are orchestrated from. This kind of structure aids debugging massively, its easy to log the sequence of actions and UI states if theres only a single location you need to hook in to.
Android MVI
Since the precious description was quite high level, it all applies for Android as well. Perhaps note that even though MVI name doesn’t exactly spell it out: on Android we typically still use ViewModel class, just as in MVVM. Again: main difference from MVVM is that theres a single state to observe and theres a single callback for user actions.
Why all this matters?
I’ve seen plenty overviews of these patterns focusing on simple dependency diagrams between the components. They do give a quick summary. However if we don’t understand the core ideas behind them, its hard to say if we are actually conforming. Additionally any adaptions would be done blindly, potentially working against the pattern. I believe this information is essential.
Footnotes
-
“MVP: Model-View-Presenter - The Taligent Programming Model for C++ and Java” ↩
-
“TWISTING THE TRIAD - The evolution of the Dolphin Smalltalk MVP application framework.” ↩
-
Not to be confused with
android.content.Intent
. ↩