The adapter pattern makes use of classes to wrap the functionality of immutable interfaces, such as the two libraries we are using:
public class MixPanelTrackingAdapter: Tracking {
Wrap the Mixpanel mainInstance, as follows:
private let mixpanel = Mixpanel.mainInstance()
Next, expose the required methods from the Tracking protocol:
public func record(event: String, properties: [String : String]?) {
mixpanel.track(event: event, properties: properties)
}
}
For Google Analytics for Firebase, it's even more trivial, as there is no singleton to wrap:
public class FirebaseTrackingAdapter: Tracking {
public func record(event: String, properties: [String : String]?) {
Analytics.logEvent(event, parameters: properties)
}
}
With those two adapters, the implementation of the adapter pattern is complete.
Two classes, MixpanelInstance and Analytics, now share the same interface through their respective adapters. It is possible now to exchange them at runtime or at startup, which helps you with vendor independence.
Through the program, only the shared tracker should be used, abstracting away the complexity and implementation details for the analytics tracking.
For example, in your AppDelegate's applicationDidFinishLaunching, you can set up your tracking library as follows:
let tracker: Tracking
if USE_FIREBASE {
tracker = FirebaseTrackingAdapter()
} else {
tracker = MixPanelTrackingAdapter()
}
Tracker.shared.set(trackingAdapter: tracker)
This pattern can be used for many use cases throughout your programs, and is particularly powerful for problems such as this one.