MVVM, you had one job to do!?

Idea 💡

In the last couple of years, MVVM has gained some reputation in the iOS community. Almost every other conference has atleast one talk on it. Almost every other blog post is talking about design patterns specially MVVM (like this one :p). All of this indicates that it must be very good at what it does. So let’s try to understand what it actually does 🤔.

Motivation 💪

When we make an iOS (or you can say in general any mobile/web) app, every screen has more than one UI component like UIView, UITextField, UILabel, UIImageView.. etc. These components need to fill themselves with a lot of business logic. Hence there is a big opportunity of tight coupling between the two. Now the coupling is bad because it,

  • increases the cost of making UI modifications
  • difficulty of unit testing such code

Thus to decouple them, we often try to use some design patterns to add some abstraction & modularity. One of such patterns is MVVM, introduced by Microsoft, which stands for ofcourse — Model-View-ViewModel. I won’t delve into the finer details of MVVM since you must be tired of reading it again & again. Instead I’ll talk about what it has to offer and where else can we apply it. If you are hearing MVVM for the first time then there are some really good documentation by Microsoft which you can checkout here or there are plenty of good posts by the community as well.

What it does? 🤔

MVVM has one & only one job — it simply separates the presentation logic from an UI. Let’s talk about this presentation logic. Every UI needs to display some data in one form or the other. Now a data can’t be directly shown because it’s saved in a raw format. So we need to apply some decorative methods on it to make it usable for the UI. So far so good but nobody talks about where is this data coming from. It could come from a persistence layer or may be an api call or may be both. So who is exactly resposible for getting this data. Since ViewModel is "ONLY" resposible for decorating the data so may be we could put our data fetching logic also in it like some core-data helpers or may be some api logic.. aaand we are back to where we started from, it starts to sound like a controller. It's time we take a step back and leave the view-model only with presentation logic.

Moment of truth! 😲

Since, while we study MVVM (or such design patterns), this grey area (i.e. who gets the data) is not very well defined so we end up putting this logic inside view-model or controller. Thus giving rise to massive-controllers or these days massive-viewmodels. But nobody said that you can’t abstract data fetching just like you just abstracted presentation logic. So what we should also learn from MVVM is we should abtract out different resposibilities and put them in a different class. It’s just MVVM doesn’t talk about data fetching but we could use the idea that led to MVVM i.e. it’s good to have single responsibility per class. We can achieve this in many ways, though right now I’ll only discuss using two simple ways — concrete classes & protocols.

Example 🛠️

In the first example we will see how can inject Datasource into UserListViewModel so that we can abstract out the logic of fetching data into a separate class and make the view-model only consume it,

struct User {

let name: String
}

// to fetch data from persistence/api
struct Datasource<T> {

func items() -> [T] {
return []
}
}

struct UserListViewModel {

// decorated data with business logic
let users: [User]

init(datasource : Datasource<User>) {
// business logic
users = datasource
.items()
.map { "\($0.name.uppercased())" }
.map { "Hey \($0)" }
}
}

We can achive similar result using protocol as well thus making UserListViewModel act like a datasource but without having the actuall logic inside it,

Update: Recently I got to know that the following approach actually has a name –– Dependency injection with Cake Pattern (small world 😅)

struct User {

let name: String
}

// to fetch data from persistence/api
protocol Datasource {

associatedtype Model
}

extension Datasource {

func items() -> [Model] {
return []
}
}


struct UserListViewModel: Datasource {

typealias Model = User

// decorated data with business logic
let users: [User]

init() {
// business logic
users = items()
.map { "\($0.name.uppercased())" }
.map { "Hey \($0)" }
}
}

What did we learn!? 🤓

MVVM is just not about separating presentation logic but also about abstraction in general (just like other patterns as well). Let’s not bloat view-models or view-controllers with too many responsiblities. Don’t try to fit your logic flow into popular design patterns. Rather simply try to find out different ways to just abstract functionalities and try to reuse them, it’s more than enough 🚀.