In this short blog, I’ll speak about how PayU migrated its Apps & SDKs to MVVM architecture to improve code testability, scalability & readability. In the past few years, we’ve done several iterations of UI revamps while our core business logic would largely remain the same (barring the new features). With our existing architecture, it was becoming difficult to deliver these changes at a pace at which our business required them to be delivered. So, we’ve heavily focused on redesigning our underlying architecture to make the code more readable, scalable & at the same time more testable by leveraging the MVVM architecture.
What were we doing?
Till now, we were following MVC pattern. Now, theoretically, it seemed a good pattern to us but as the code base increased and more and more UI revamps were introduced we realized that the codebase was becoming unmanageable for two reasons:
- With each UI iteration, we were changing not just the UI code, but also the business logic. Which directly pointed out that somewhere our UI was not separated from the business logic.
- As the codebase increased our unit-testable code was reducing — meaning we were relying more on end-to-end testing. This in itself was a bad sign as our testing pyramid was inverted.
As we tried to understand the problem — we realized that both the View & Controller depend on the Model. Also, the activities/fragments were holding both the UI logic as well as the controller logic. Because of this, our unit-testable code was diminishing & the code was becoming unmanageable.
MVVM as a savior
We started our lookout for better architecture and this is when MVVM came in as a savior. Even, Google ❤ MVVM.
MVVM stands for Model-View-ViewModel:
- Model: It represents the data layer of the Application
- View: It represents the UI logic of the Application
- ViewModel: It is a Model for a View. It acts as a bridge between the View and Model & does not have a direct reference to the View
MVVM architecture removes the tight coupling between each component. The children don’t have a direct reference to the parent, they only have the reference by observables. Notice that each component depends only on the component one level below it. This design creates a consistent and pleasant user experience.
Another important component in the diagram above is the Repository module. It handles data operations. It provides a clean API so that the rest of the app can retrieve this data easily. It knows where to get the data from and what API calls to make when data is updated. You can consider repositories to be mediators between different data sources, such as persistent models, web services, and caches.
LiveData is an observable data holder. Other components in your app can monitor changes to objects using this holder without creating explicit and rigid dependency paths between them. The LiveData component also respects the lifecycle state of your app’s components — such as activities, fragments, and services — and includes cleanup logic to prevent object leaking and excessive memory consumption.
- Separation of concerns: It’s a common mistake to write all your code in an Activity or a Fragment. The UI-based classes should only contain logic that handles UI and operating system interactions. By keeping these classes as lean as possible, you can avoid many lifecycle-related problems. MVVM lets you ensure that each type of class is responsible for only one type of task.
- Testability: User interface and interactions can be tested using instrumentation test cases (Espresso Library). ViewModels & Repository can be tested using the JUnit test.
- Scalability: You can easily introduce UI revamps. New features can be added without the need to refactor or change much of the existing codebase. We can plug and unplug the components with much more ease.
- Manageability: The codebase is much more manageable – All your UI logic is in one place, your business logic & your data logic at another place.
- Getting started with MVVM & adding new features may require some experience with the pattern. There is a steep learning curve, relatively.
- Following the MVVM pattern, you might end up creating more Java classes.
While building any application it’s important to pick the right architectural pattern — there are no fixed “fits all” patterns.
In our case MVVM proved to be a useful pattern as we had a UI rich product that was subject to frequent UI revamps, we wanted to focus on the testability aspect of the product and we also wanted that there is a separation of concerns so we could do UI revamps by just changing relevant components of the codebase without touching rest of the codebase. MVVM architecture was just the perfect fit for us!