The Meltwater Mobile application development team was formed in 2014 and has grown from 3 to 18 members. We build native apps for both iOS and Android platforms. Usage of the native apps has grown in sync with the growth of the team size.
In this post we want to share how we have reinvented and refocused the way we build our native apps by adopting VIPER as our foundation. Part 1 of this two part series discusses how we arrived at VIPER as our underlying architecture and why it turned out to be the right choice for us. The journey to VIPER was not easy, nor was it one we wanted to take, but now that we have crossed over the chasm of fear we are on a solid path to future success.
If your team is considering moving to VIPER you might just be convinced to “go for it” by reading our story.
An Unexpected Journey
One of the most common approaches companies take to build a mobile application is to throw a developer at a project and have that developer simply write whatever code necessary to get the app to work. If the project is successful it gets another programmer, and they work diligently to add features to the code in any way possible. As the project gets more successful the project grows, and grows … like a weed. Ultimately what started out as a simple approach to getting the job done turns into handcuffs that force you to work around or ignore proper coding techniques that would lead to healthy long term life of a codebase. Even worse, any new code has the potential to be poisonous to regression tests. These routine exercises become susceptible to endless hours of debugging caused by crossed wires from those simple changes.
Sometimes projects that have originated in this way realize the dangerous path they are on before it gets into a worst case scenario. Those project teams work to patch the crossed boundaries, establish good patterns and best practices, and try make better code moving forward that adheres to some form of a sensible architecture.
Furthermore, projects that experience the preceding refactoring epiphany have to perform their code exorcism for multiple platforms (iOS & Android). After this multi-headed monster is defeated there is usually a large improvement of code quality, app functionality, and developer performance across all platforms.
As a result of this improvement, faith in the project grows, vision for the product becomes wide, expectations grow, and excitement among the team climbs. To support this new enthusiasm the development team grows and……now we have a problem. How do you get these new developers (some with tons of experience & some with no experience) to understand all of the proprietary coding olympics needed to perform at the newly accustomed high level. Oh, and by the way, some developers are going to work on both platforms.
This is what happened to my team, and I was a platform crosser.
What did we do?
They only babble who practise not reflection. ~ Edward Young
With our problems becoming obvious we realized that we needed to understand how we got into this situation as well as what was preventing us from climbing out of the hole we dug ourselves into. The things that we determined as most responsible for our blocking progress are very common in many software development teams. We simply needed to take the time to recognize them.
Our team needed to think more broadly as individuals, think more concisely as a group, develop for predictable outcomes, and simplify the decision making process.
Specifically we needed to:
- Simplify our files - We had huge files that were individually doing too many things
- Code for more reuse - We were doing too many platform specific coding techniques
- Unit tests - Figure out some way to enable us to really do this
- Think the same regardless of platform - No more coding in a vacuum
- Adopt a concrete cross platform architecture - Step 1
- Less debating, more coding - Lots of experience == lots of opinions
Architecture Buffet
Guess, if you can, and choose, if you dare. ~ Pierre Corneille
Once we understood our needs, the first step in meeting our objectives was to put into place a legitimate architecture that would be easily understood by any developer stepping into our codebase. There are many options out there with a ton of different pros and cons; MVC, MVVM, MVP, VIPER, etc… Additionally there are many articles on the web that debate these architectures ad-nauseum. Rather than repeat all of that I will simply share the result of our digging:
- MVC == MassiveViewController
- MVP/MVVM gives us structure around separating the view and the data model and makes unit testing a bit easier
- VIPER == A lot of overhead for simple programming tasks.
When we reviewed the different architectures, MVVM seemed like a good compromise between adding value, defining roles, and not being over-burdensome. MVVM does a great job of avoiding the “MassiveViewController” problem without the seemingly massive overhead associated with VIPER. To be honest, VIPER scared us. It was so formal, very tedious, and somewhat confusing. So, we decided to go with MVVM … until we didn’t.
After making the MVVM decision, we jumped into our first design discussion to move our code to the new architecture … and the debates continued. MVVM does not provide a lot of structure for the rest of an application’s code that does not involve the view.
For example there are no guides for:
- how navigation occurs,
- how API calls are made,
- how cache information is stored and retrieved,
- how to do real valuable unit testing
It was clear that MVVM was not going to prevent our problems from continuing. If we were going to commit to changing our ways for the long haul we had to force ourselves to adopt the proper structure. We needed to go big and bold. We had to adopt VIPER!
What is VIPER?
Intellectuals solve problems, geniuses prevent them. ~ Albert Einstein
“VIPER is an application of Clean Architecture to iOS apps. The word VIPER is a backronym for View, Interactor, Presenter, Entity, and Routing. Clean Architecture divides an app’s logical structure into distinct layers of responsibility. This makes it easier to isolate dependencies (e.g. your database) and to test the interactions at the boundaries between layers.” (source: ojbc.io - Architecting iOS Apps with VIPER)
There are many articles that describe what VIPER is. Hence I will focus on telling you what VIPER does for us.
VIPER is superb at defining roles for code components, dictates explicit lines of communication between code components, and forces you to establish a convention based structure for where code lives in your repository.
The VIPER Rules (Simplified)
- When developing a feature the code for that feature lives under its own folder (called a module) in the repository.
- If code for a module is NOT specific to that module then it lives under the “Common” module.
- The Router is responsible for:
- bootstrapping the VIP components of a module
- all navigation within a module, and
- navigating to a new module.
- The View is responsible for
- rendering data and information that it gets from the Presenter.
- Receiving interactions from the end user to pass to the Presenter
- Receiving system events to pass to the Presenter
- The Interactor is responsible for Entity retrieval regardless of where that Entity comes from. Entity can come from (among other places):
- A database
- A cache
- Profile settings
- Remote API
- Broadcast notifications
- The Presenter is “the Hub” between the VIPER components.
- The Presenter is responsible for:
- decision making
- calling into the router and interactor
- holding on to data:
- that supports the view,
- that is passed to the interactor
- that is passed the router.
- business logic
- analyzing information
- notifying the view for rendering
- responding to events generated from the view
- The Presenter should not have any code that is platform specific.
- The lines of communication across VIPER components cannot be subverted, short-circuited, or chained.
The bottom line is that the rules of VIPER are meant to prevent many of the problems that we were looking to solve. Now that we were convinced VIPER was the right architecture for us, it was time to start designing components and writing some code.
Part 2 in this series describes what we actually did to accomplish a successful move to VIPER, so stay tuned for that.
If you have any questions or suggestions please comment below.