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

Caio Zullo

I've been writing software since 1998, professionally since 2006, and on Apple platforms since 2009. I love building robust, well-engineered, and beautiful applications and coaching developers to achieve their best potential.