iOS: MVVM design for Screen having multiple web services/multiple sources of data
Choosing a design for a screen which has multiple sources of data, multiple child view components, multiple web services is always a challenging thing. Recently I had given more than 20 interviews where I had been asked to make designs for such scenarios and also got lots of pre-interview assignments to make complex screens using MVVM.
Here are some key points that interviewers/assignment givers are actually interested in.
- They are actually interested in practical implementation and want to know how communication will happen among layers.
- They want to know the number of classes/interfaces you will take to design it.
- Whether your code is scalable/unit testable
- Whether your code follows Engineering basic principal OOPs, SOLID etc.
- Whether your code is clean, concise and modularise.
To start with screen design lets understand first what MVVM is.
MVVM — MVVM(Model-View-ViewModel) is a software design pattern which separates the code of User interface from code of business logic/testable logic or any custom logic to manipulate data. Basically the View model is converted model/business class from the data model.
If we talk about simple and small view design then communication would be like below:
From the above image it shows that MVVM has bidirectional communication between View-Model & Model and Model & View Model. And Also View -> View Model & View Model -> View. If we see the connection between the View to View Model symbol is something different which is called Binding. That means changing the state of the View model, the view’s state should be changed and vice versa.
Confusion on Binding in iOS App: Sometimes developers get confused about what is binding, should we use any binding technique?. You don’t worry about binding techniques, it is just a way to make communication between view and view model. It means just sending an event to View if something changed in VIew model or if some event happened in View, notify to view model. This communication can be achieved in many ways.
To achieve binding, developers can create their own technique to listen to activity from View to ViewModel or View model to VIew if they are not aware about reactive programming like RXSwift/Combine where state changes automatically if binded with each other .Binding can be done by delegate(by passing weak reference of view without knowing View) or block, property observer etc.
- If you understand the binding concept correctly then we can update above image as below.
If we convert above image into classes and entity, we can have below:
If we check the above code, the view is tightly coupled with the view model which violates dependency inversion principle. We can change above code as below.
Where ViewDataInterface is an abstraction for view models. If we make our view controller this way, Any view model can be passed to our view which implements ViewDataInferface abstraction.
Some time making protocol/interface overkills the code so you can directly use view model reference in View Controller it’s up to you and your requirement.
View Model Class
Better if we convert the above classes and connect using class diagrams. It will look like below.
Hope above explanation is already told about MVVM.
Let’s come to the problem for which this tutorial was written.
Problem statement: Need to make an app that will search for a Cocktail based on the input given by the user. Search results would be a grid as shown in the below given screenshot. A few search keys are “Vodka, “Rum”, and “Mojito”. You need to consider the below points.
A few of the UI elements in the attached screenshot like “Let’s eat quality food”, “Near restaurant” and a row below it are static and should be hardcoded. The app should follow the MVVM design pattern and the language used should be Swift.
- If we break the screen into sections, we have 3 sections. First section contains header with textField, second section contains restaurant section which data comes from Restaurant Service and Third section contains drink section which data comes from DrinkApiService.
Solution — From the screen it looks like it is a collection view in 3 sections. If we follow MVVM we need to consider the whole screen as one Main View. Let’s take it as a HomeViewController as the main View.
So our first 2 classes would be HomeViewController and HomeViewModel. Lets build skeleton for this 2 classes
- So the collection view would be the main content view which data would be supplied by HomeViewModel. But Collection view is also made with small-small child view which would need its own view model to render it.
- To populate collection view lets create view model interface.
Where some of protocol used listed below
Above protocols are used to make view controllers as independent as we can. Now lets see how HomeViewController will look like
- If we see the above view controller code and find that there is no business class instance except ViewModel. This means all business logic required in the View controller would be fulfilled by view model.
- Now the question is where do we write code for networking, database, caching, tracking/analytics. These things are always debatable. Some developers keep an instance of the caching/network handler/database handler instance in the view controller and start entry of these from here. Some keep instances of these classes in View model and View will ask view model to perform tracking, caching and View model will redirect code in their responsible class.
- My Preference — My preference would be to keep instances of network client/analytics client in View Model instead in View controller so that Communication between View and View model always be bidirectional and no other layer comes between View and View Model.
- Suppose if any web service class for fetching data to display in View/View controller, View controller will notify to View model to fetch data and provide displayable data and View model will ask to network layer classes to get data and returns server data to View model so that View model gets server data and process into displayable format and supply back to View/View controller.
- In our problem UI there are 2 web service calls which are triggered from the View controller via ViewModel.fetchRestaurent() and ViewModel.fetchData(querry: searchbar.text). Lets how our view model will look like.
https://gist.github.com/jagelooyadav/04bb16a324e109b830ae9ceeb15a5058#file-homeviewmodel-swift
Now the question is what forms of data ViewModel will supply to ViewController because the view controller has a collection view which has many subviews.
Since data is coming from different- different sources, we have to create some common/generic approach to make similarity in those data to supply uniform data to Collection View instead of making child view model instances in main view model and storing in it.
Let’s take one generic model which can handle all api called in the main view model. Since the collection view is divided into sections, we will make a section object for each section.
struct Group<T>: Section { let groupType: GroupType var title: String? let groupData: [T]}
Where T is generic data returning from web services. Section is protocol which will have groupType/dataType and section title property which needs to implement each section/group object
Suppose if service1 return Restaurant object list group would Group<Restaurant> and if service2 returns Drink data, it will be Group<Drink>
Also first section is not required any data all are hard code so we can make group like Group<Void>
So here are section objects created for each section.
Section for main header including search field
let screenHeader = Group<Void>(groupType: .screenHeader, groupData: [])
Section for restaurant
let dataSupliedFromRestarentApi: Restaurent = someDatalet defaultEntry = Group<Restaurant>(groupType: .defaultEntry,title: “New Restaurant”,groupData: [dataSupliedFromRestarentApi])
Section for Drinks
Let dataFromDirnkApi: [Drink] = someDatalet section = Group<Drink>(groupType: .otherEntries, title: “\(data.drinks.count) Items found”, groupData: dataFromDirnkApi)
If you see the above section object I am not sending the child view model to the Main View model to populate its child but I am sending raw data to the Main view model. We can child view model objects here in the main view model but it will have some calculation unnecessarily so better to send raw model to main view controller where child view model can be created with this raw data.
So in cellForItemAt function of view controller we can create child view model for cell has below:
let section = viewModel.sections[indexPath.section]If section.groupType == .entriesData, let group = section as? HomeViewModel.Group<Drink> {let drinkData = group.groupData[index.row]cell.viewModel = GridCardViewModel(drink: data)}
As I said this calculation of GridCardViewModel creation could be done in Main view mode itself but it will put unnecessarily burden in main view model calculation.
- Idea is to avoid storing all child view models into main view model instead supply raw data to main view there child view model can be created during child view initialization or updation. It will allow child view updates independently instead of reloading the whole View model for just reloading part of the UI.
- On the basis of the above explanation I have made a generic diagram to deal with multiple web services in MVVM design pattern please see below diagram.
To see complete code you can check out code here
Conclusion: Hope this tutorial and code helped you in writing testable code into MVVM and if any assignment/interview you get in MVVM you would crack it. If any confusion and question you have, you can directly comment here or email me on jageloo.yadav@gmail.com Always be there for help. Happy reading!