In a previous piece, we explored the design journey of our InRide screen for drivers. As technologies and needs evolve, our product does too. HamidReza, our Product Manager, came up with the idea of embedding navigation capabilities right within our existing In-Ride screen, freeing our drivers from dependency on third-party navigation apps. 🗺️
Behnam had already been investigating the prerequisites of the navigation UI for some time, and now the task was to integrate it into the ride screen 🌐. We realized our UI depended on two domains: ride and navigation. During a model design meeting 📈, we decided to create a separate ViewModel for navigation, aiming to disentangle varying responsibilities. The immediate problem, however, was how to share the necessary state between these ViewModels.
The question arose: should we create different use-cases and a shared repository to handle the state between these two ViewModels? Another concern was how to invalidate stale data in this shared repository.❓
We took a different approach to tackle both problems in a single ViewModel.
- Separation of Concerns: By crafting delegate classes for each functionality like navigation and ride-specific details, we could work on each component in isolation, making it easier to test and develop. 🛠️
- Shared State Management: We realized that our main ViewModel could act as a mediator 🤝, stitching together these isolated parts to complete the puzzle. It allowed us to share state data without worrying about invalidating stale data or building a hefty repository.
private val rideDelegate: RideDelegate,
private val navigationDelegate: NavigationDelegate
) : ViewModel(),
NavigationDelegate by navigationDelegate,
RideDelegate by rideDelegate,
// ViewModel logic here
One major advantage of using Kotlin property delegation in our ViewModels is the fine-grained testability. By isolating responsibilities into delegate classes, we make it easier to test individual components. These smaller “System Under Test” (SUT) components offer clearer insights into which part of the system may be causing issues if a test fails. 📈
Consider the alternative: if we only tested the main ViewModel, our SUT would encompass both the ride and navigation logic. A failure in one could potentially compromise the entire test suite for the ViewModel. Separate, testable delegates allow us to pinpoint the exact cause of any test failure, making debugging and future modifications more straightforward.📍
Using Kotlin delegates, we were able to compartmentalize different functionalities, making it easier to write unit tests and maintain our code. More importantly, we sidestepped the complications often associated with using multiple repositories, use-cases, and the challenges of cache invalidation and state sharing. 🚀