Until recently we've only needed to report analytics events for Venmo, to Amplitude, via mParticle.
To that end, iOS uses the following abstraction for tracking analytics events in feature code.
protocol AnalyticsEventTracker {
func track(_ event: some AnalyticsEvent)
}
Analytics events are defined as having a name, an entity (which is used for general categorization), and a collection of properties with string keys and scalar values.
public protocol AnalyticsEvent {
var name: String { get }
var entity: String { get }
var properties: [AnalyticsEventProperty] { get }
}
Sending events to PayPal requires more data than our AnalyticsEvent
protocol allows. Specifically, PayPal tracks the type of the event. One of the following values:
- activity -
"ac"
- click -
"cl"
- impression -
"im"
- redirect -
"rd"
- userAction -
"ua"
To represent events going to PayPal, we refined the AnalyticsEvent
protocol with the additional event type requirement.
public enum PayPalAnalyticsEventType: String {
case activity = "ac"
case click = "cl"
case impression = "im"
case redirect = "rd"
case userAction = "ua"
}
protocol PayPalAnalyticsEvent: AnalyticsEvent {
var eventType: PayPalAnalyticsEventType { get }
}
And added a new tracker protocol for PayPal events.
protocol PayPalAnalyticsEventTracker {
func track(_ event: some PayPalAnalyticsEvent)
}
Some features want the ability to send the same event instance to both PayPal and Venmo. PayPal event trackers can be combined with Venmo event trackers to create instances that dispatch to both locations.
let payPalTracker: PayPalAnalyticsEventTracker
let venmoTracker: AnalyticsEventTracker
let combinedTracker = payPalTracker.combined(with: venmoTracker)
Ideally we'd go back to having just one event tracker protocol. For that we should move the eventType
requirement from PayPalAnalyticsEvent
up into AnalyticsEvent
. PayPalAnalyticsEvent
and PayPalAnalyticsEventTracker
should be deprecated.
protocol AnalyticsEventType {
case activity = "ac"
case click = "cl"
case impression = "im"
case redirect = "rd"
case userAction = "ua"
}
protocol AnalyticsEvent {
var name: String { get }
var category: String { get }
var eventType: AnalyticsEventType { get }
var properties: [AnalyticsEventProperty] { get }
}
Making this change alone would be API breaking for hundreds of events defined across modules. To prevent this, we should provide a default implementation of eventType
that returns a reasonable value until events can be manually audited for the correct type.
extension AnalyticsEvent {
var eventType: AnalyticsEventType {
.userAction // Or something better?
}
}