Decoupling Analytics from MVVM components | iOS Lead Essentials Community Q&A
/In this episode, Caio replies to a question we received from Catalin in the private iOS Lead Essentials Slack community:
“What are your thoughts regarding an architecture for adding analytics for a push notification event?
For example, how can I send a track event like "push_failed_to_open_itemId" if it fails to load an ItemId received in the push notification?
I want to avoid passing around something like fromPushNotification: true
into all layers: View controller, View model, Service, etc...
Or using UserDefaults to store a temporary value that I can check in my View Model or Service.”
To illustrate, let’s create a View Model
with a method loadItemDetails
:
final class ItemDetailViewModel {
...
init(itemID: String) {
self.itemID = itemID
}
func loadItemDetails() {
...
}
}
The View Model
loads the details from a service. If the request fails, it has to track the error with the failed_to_load_itemId
key:
func loadItemDetails() {
service.load(itemWithID: itemID) { result in
switch result {
case .success:
...
case .failure:
analytics.track(key: "failed_to_load_itemId", value: itemID)
...
}
}
}
Now, imagine there’s a new requirement. If the itemID
came from a Push Notification, it has to use the key push_failed_to_open_itemId
.
So, since the View Model
decides which key to use, it needs to know the provenance of the itemID
(either from the normal app flow or from a push notification).
But we don’t want to pass booleans around to let the View Model
know where the itemID
came from. Otherwise, we’ll have to change multiple components every time there’s a new analytics requirement.
We also want to avoid temporary values in UserDefaults or any other global state that can lead to other issues such as race conditions.
To solve this challenge, you can move the decision up the object chain using Dependency Injection. Or better yet, decouple your View Model
from analytics to eliminate this problem forever.
Watch now the full video to find out clean strategies on how to move decisions up the chain and how to decouple analytics and other cross-cutting concerns from your MVVM components. As a bonus, you’ll also learn a clean way to do it using mapError
in Combine or RxSwift.
Subscribe to our YouTube channel and don't miss out on new episodes.
References
- Clean iOS Architecture pt.1: Analytics Architecture Overview
- Design Patterns in iOS/Swift iOS Lead Essentials Podcast #014
- iOS Composition Root iOS Lead Essentials Podcast #015
- Join us in the Essential Developer Academy