Unpopular Opinion: MVVM is wrong
Remember back in 2017, the first days of Google having an opinion about application architecture because countless developers were begging them for advice? I do. As they laid out their opinions at Google I/O that year, I remember thinking the same 2 things I’ve thought at every Google I/O since then, which were:
- I’ve already solved this problem
2. I like my way better.
Before we dive in, let’s be clear: I’m about to tell you why I think Google’s approach to app architecture is garbage. Also, why I don’t (and will hopefully never) use Google’s architecture components, data binding, ViewModel system, or live data.
To say the least, there is certainly, and always will be, a place for all things Google in Android development, but if you love all these tools and ideas, you might wanna stop here, because you aren’t going to like what I have to say. As for the brave remainder, read on. What follows is simply my opinion.
MVVM is too complex
MVVM is an acronym that means Model-View-ViewModel. You would think that the three part system of MVVM is no more complex that MVC, MVP or MVI… but you’d be wrong. See, there’s a sneaky fourth component to MVVM that goes unmentioned by the acronym, and that’s the Binder.
Without the Binder component, the architecture simply doesn’t work. In MVVM, it’s really key that the Binding layer be automatic — meaning, the developer doesn’t need to write it. If your MVVM implementation includes a manual binder that the developer has to write in order to propagate the data from the ViewModel to the View, then guess what? Your ViewModel is actually a presenter.
The binding layer in true MVVM usually comes from some third-party or built-in framework, and usually involves code that is written by that framework to create the binder for you for each screen or view. There are two problems here:
- You need to be developing inside a system with code generation for this to work, and
- The triggers for the binding code can be hidden or difficult to read and understand.
Let’s take these one at a time. The most compelling consequence of number 1. is:
MVVM Isolates Your App from your other App
If you are building an Android app with no iOS equivalent, stop reading. Go use MVVM to your heart’s content and don’t worry about anything, ever. You probably sleep great at night.
But if your organization also has an iOS app, here’s something to consider: MVVM can’t truly be used on iOS. There’s no first-party code generation for Swift, so you’ll be writing your own binders, which means you’re going to be implementing MVC or MVP and just calling it MVVM. Or, what most developers do, you’re going to have completely separate architectures and code for each platform.
If you’re totally ok with that, and see no problems or costs with that strategy, stop reading now, go have a drink and enjoy the vacation that is your life.
But, there are plenty of pretty good reasons that you might want your architectures for the same app on different platforms to match. With Swift and Kotlin syntax drifting closer and closer, if your architectures have the same structure, a lot of your business logic (and by extension, your unit tests) can too. That could mean cross platform code reviews, and even someday, code sharing.
With wholly different architectures, all that goes away, and you have two entirely separate teams, writing entirely different code, with different tests, that do the exact same thing.
For the second part of my argument, let’s consider how Google’s recommended MVVM architecture works. It uses the Jetpack DataBinding library.
To use the library, you add some annotation and information to your layout XML files, and that triggers the creation of binder code when you build your project. While the code they generate can be quite complex, the annotations themselves can also be complicated, to the point where it starts to amount to adding code to your layouts. This means that, in addition to your actual Kotlin or Java code, there is now an additional place for bugs and failures to occur. It also conflates the purpose of the layout (to provide design and organization to UI components on the screen) with the purpose of code (the logical operations that govern which UI elements appear, the content they contain, and the effects of interactions.) This is the same reason I’m not a fan of Android’s Data-Binding to begin with — it essentially adds a new location for code that the IDE doesn’t do a great job of making discoverable, and that the compiler doesn’t give you much help with when something goes wrong.
Here’s an example of a two-way data binding annotation using the system:
This annotation now implies the existence of the NumberOfSetsConverters class, which you also have to create and populate with adapting logic to hook up that Edit Text. Already, there are plenty of places for bugs and failures, and you still have to write more code. And since you have to write some of that binding code anyway, how much benefit are you reaping from the whole system in the first place?
It wasn’t that long ago that Google’s position on app development was: “you figure it out,” and I, for one, miss those days. That’s not to say that I don’t appreciate all the new tools that Google has built to make Android and app development better. But the fact is, if you’re building an experience on both major platforms, the architecture that Google recommends shouldn’t be advertised or regarded as “best practice.” It’s simply one of many options, and in the end, the choice of what’s best is up to you.