There is no more heated battle in the Android community than the choice of which is the best architecture. Which is the definitive best and the one that EVERY and I mean EVERY developer should go for.
But really in the end, it all boils down to personal prefere-
There must be a best. This isn’t art. This is engineering. This is SCIENCE.
So we will look at the 3 main competitors of Android Architecture in 2020: MVP (Model-View-Presenter), MVVM (Model-View-ViewModel), and MVI (Model-View-Intent).
And we will be analysing each of their advantages, disadvantages, ease of use, and support to see which is the objective best architecture for Android.
But first, let’s look at what they all perform well.
Separation of Concerns – Each component makes use of multiple components that each have defined roles in each pattern. All of these patterns have components that individually adhere to domain, data, and presentation.
Testability – Because each component does its own thing and has their coupling structure well defined, all these patterns allow you to test each component in isolation. One pattern may require you to mock one more object than the other, but this is very minimal effort and very much negligible.
Modularity – Each architecture should allow you to switch between different implementations of components while other components remain intact.
Now for the differences in each architecture.
The Model-View-Presenter is the oldest of the 3, as it was born out of the need to solve an inherent tight-coupling problem with the ancient MVC architecture which considered it’s View as the XML layout and its Controller as the Activity coupled to it.
The MVP pattern instead considers the View to be both the XML and Activity combined and the View component can be either the Activity class itself or a separate component that interacts with the UI elements in said activity, and should strictly be used for this purpose with minimal logic.
The logic is thus handled in a new component known as the Presenter which acts as a bridge between the View and the Model. The Presenter also decides what happens when a user interacts with the view, such as clicking a button, and thus, would have a method to handle each possible case.
The Model acts as a provider of data and is responsible for communicating with both the network and the local db.
This architecture has it so the Presenter knows of both the View and the Model but those components don’t know of any other component in the architecture, thus the Presenter handles all of the communication between the components.
MVP has a problem though, and that is the Presenter’s life is coupled to the Activity’s. This creates opportunity for the activity to get leaked when the Presenter performs long running tasks that get interrupted when the activity gets destroyed, like through configuration configuration changes (e.g. screen rotation) or the system’s natural processes.
And while the performance overhead brought about by this isn’t so bad in most cases, it also does create opportunity for crashing your app as the View will try to interact with an activity that no longer exists. While there are solutions to this problem, it carries the trouble of 1) being aware of this problem and 2) additional boilerplate.
Where MVP does shine though is in its simplicity. The other architectures introduce a layer of complexity that MVP simply doesn’t have. This is most prominent when implementing the pattern with dependency injection. With MVP, you’ll find minimal trouble and effort doing so. With the other architectures, not so much.
- Simple well known architecture even outside of programming and thus, has an easier learning curve for most
- Easy to implement with DI
- Presenter’s life being tied to the activity creates opportunity for memory leaks and crashes
- Additional boilerplate and constant awareness of the above problem is required to fix it
- Doesn’t offer much that the other architectures don’t
The Model-View-ViewModel is newer than MVP, but has lived long enough to gain strong popularity among the community.
In this architecture, the role of the View and the Model are the same as they are in MVP. The View’s sole responsibility is to interact with UI elements, and the Model’s sole responsibility is to be a provider of data.
The new component here is the View Model. This is a component also handles both logic and using the Model to retrieve data but unlike the presenter, it can survive configuration changes and isn’t strictly tied to the lifecycle of the activity. This solves the main problem MVP has of possible crashes and memory leaks, and carries the additional advantage of multiple views watching the same View Model instance.
How is this second advantage feasible? The View Model relies on a type called
LiveData, a variable which can be both observed and have its data updated while maintaining the same instance of the variable. This means that the View Model only needs to update the data carried by the
LiveData, and the View (or Views) observes for changes in this
LiveData within themselves.
This is further enforced by the knowledge structure of the components in the MVVM architecture. In the diagram below, each component only knows of the component directly above it. The View knows of the View Model, and the View Model only knows of the Model.
Yes, it’s possible for other architectures to use
LiveData as well, but no other architecture can really get as much out of it as MVVM does.
Due to its integration however, MVVM relies heavily on Android Architecture Components, and suffers from a case of “how should I implement this pattern” syndrome more than the other patterns due to different developers’ ways of using these architecture components in different ways and combinations.
On top of that, this is quite arguably the architecture that’s the hardest to implement together with Dagger (or dependency injection in general) because of the need to use
ViewModelProvider (or the
by activityViewModels delegate from the fragment-ktx dependency) to attach the View Model to the activity.
- View Model can survive lifecycle and configuration changes of the activity
- Multiple activities / fragments can use the same View Model simultaneously
- Heavily reliant on Android Architecture Components
- Suffers from “how should I implement this pattern” syndrome more than the other architectures
- Hardest to implement right with DI
The freshest of the 3 architectural patterns in this battle royal but it is nonetheless one that has become quite prominent in the community. Does it however, stand up to the big dogs?
The Model-View-Intent comprises of a Model, View, and Presenter. Intent in this architecture doesn’t stand for a component, nor does it refer to the
Intent object from the Android SDK. Rather, it stands for a desire to perform an action. A use-case if you will. An intent.
So how does that tie in to the architecture? You’ll have to get the whole picture of how the pattern works, so read on.
MVI differs from the other patterns in that the Presenter only ever sends one object to the View, commonly referred as a View State. The View State is meant to carry all the information the View needs to render itself. The Presenter can update the View State as it fetches new data, but the View receives no other data from the Presenter than this one View State. It strictly adheres to the Single-Source-of-Truth (SSOT) programming principle.
Thus, an Intent in this case refers to a complete flow of actions, from the Presenter asking the Model to retrieve data, to updating the View State, to passing the updated View State to the View for rendering. This intent can be started from the presenter’s creation, or result from a user interaction such as button click, and when the View successfully renders itself from an updated View State, the intent is resolved.
This carries the obvious advantage of having only a single object to observe, thus managing running tasks becomes easier than it could ever be. Easier than even an offline application? Yes. That’s how easy it is.
The knowledge structure is the same as that of MVP, as this could be seen as an enhancement to the MVP architecture.
This ‘enhancement’ isn’t without consequences though. How does the Presenter handle updating the View State with new data while gracefully keeping the data it already contains? The answer to this is another concept of the MVI pattern called State Reducers. These are objects that handle the merging of new data with the existing View State, and are derived from a timeless programming concept of the same name.
In more complex activities, the complexity of the reducer scales along with it and can very quickly become an object requiring high skill to maintain, and even higher skill to create properly.
- View State object makes it clear to see how the View should render itself
- Single Source of Truth adherence
- Easier to manage one observable stream between the View and Presenter rather than many
- State reducers introduce a high level of complexity to both create and maintain
- Suffers from the same lifecycle disadvantages as MVP
Each of these architectures offer plenty in the domain of separation of concerns, testability, and modularity, but each of them have advantages and disadvantages that they individually offer.
In a nutshell, MVP is simple but involves a heavy lifecycle problem and doesn’t offer much that the architectures don’t, apart from its simplicity.
MVVM relies heavily on Android Architecture Components and can be difficult to implement correctly with DI, but can survive lifecycle and configuration changes, as well as having multiple View components making use of the same View Model at the same time.
MVI offers a strong adherence to SSOT and the View State provides a good data representation of the View, but has the heavily complex need for creating and maintaining State Reducers, on top of having the same lifecycle problem as MVP.
So after carefully weighing the advantages and disadvantages of each architecture, the council has decided that the objective best architectural pattern for Android in 2020 is…. MVVM!
The lifecycle problem of MVP and MVI is a big deal. This creates easy opportunities for memory leaks, bugs and crashes. MVVM solving this is a huge advantage.
On top of this, the fact that MVVM can have multiple Views making use of the same instance of a View Model is a unique advantage that the others simply don’t have. If you had multiple fragments, you would have to create a Presenter for each of them.
MVI does make use of View States to represent how the View should render but the View Model fulfils that exact role within the class itself. The state of the LiveData within the View Model should also be a representation of how the View renders itself.
Now what’s the main disadvantage of MVVM, DI complexity. MVI also brings a high level of complexity with state reducers. The difference is once the DI is set up in an MVVM app, maintaining it requires minimal effort. The complexity curve has been conquered. With MVI, you have to create State Reducers for every activity and what the State Reducer needs to accomplish in each case will be different. MVP doesn’t have this disadvantage of complexity, but the advantages that MVVM brings to the table more than makes up for it.
The winner is MVVM.