Sources/Confidence/ConfidenceAppLifecycleProducer.swift (87 lines of code) (raw):
#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)
import Foundation
import UIKit
import Combine
public class ConfidenceAppLifecycleProducer: ConfidenceEventProducer, ConfidenceContextProducer, ObservableObject {
public var currentProducedContext: CurrentValueSubject<ConfidenceStruct, Never> = CurrentValueSubject([:])
private var events: BufferedPassthrough<Event> = BufferedPassthrough()
private let queue = DispatchQueue(label: "com.confidence.lifecycle_producer")
private var appNotifications: [NSNotification.Name] = [
UIApplication.didEnterBackgroundNotification,
UIApplication.willEnterForegroundNotification,
UIApplication.didBecomeActiveNotification
]
// Storage Keys
private static var userDefaultVersionNameKey = "CONFIDENCE_VERSION_NAME_KEY"
private static var userDefaultBuildNameKey = "CONFIDENCE_BUILD_NUMBER_KEY"
// Context Keys
private static var versionNameContextKey = "app_version"
private static var buildNumberContextKey = "app_build"
// Event Names
private static let appLaunchedEventName = "app-launched"
private static let appInstalledEventName = "app-installed"
private static let appUpdatedEventName = "app-updated"
public init() {
for notification in appNotifications {
NotificationCenter.default.addObserver(
self,
selector: #selector(notificationResponse),
name: notification,
object: nil
)
}
let currentVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
let currentBuild: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
let context: ConfidenceStruct = [
Self.versionNameContextKey: .init(string: currentVersion),
Self.buildNumberContextKey: .init(string: currentBuild)
]
self.currentProducedContext.send(context)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
public func produceEvents() -> AnyPublisher<Event, Never> {
events.publisher()
}
public func produceContexts() -> AnyPublisher<ConfidenceStruct, Never> {
currentProducedContext
.filter { context in !context.isEmpty }
.eraseToAnyPublisher()
}
private func track(eventName: String, shouldFlush: Bool) {
let previousBuild: String? = UserDefaults.standard.string(forKey: Self.userDefaultBuildNameKey)
let previousVersion: String? = UserDefaults.standard.string(forKey: Self.userDefaultVersionNameKey)
let currentVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
let currentBuild: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
if eventName == Self.appLaunchedEventName {
if previousBuild == nil && previousVersion == nil {
events.send(Event(
name: ConfidenceAppLifecycleProducer.appInstalledEventName,
data: [:],
shouldFlush: shouldFlush)
)
} else if previousBuild != currentBuild || previousVersion != currentVersion {
events.send(Event(
name: ConfidenceAppLifecycleProducer.appUpdatedEventName,
data: [:],
shouldFlush: shouldFlush)
)
}
}
events.send(Event(name: eventName, data: [:], shouldFlush: shouldFlush))
UserDefaults.standard.setValue(currentVersion, forKey: Self.userDefaultVersionNameKey)
UserDefaults.standard.setValue(currentBuild, forKey: Self.userDefaultBuildNameKey)
}
private func withLock(callback: @escaping () -> Void) {
queue.sync {
callback()
}
}
@objc func notificationResponse(notification: NSNotification) {
switch notification.name {
case UIApplication.didBecomeActiveNotification:
track(eventName: Self.appLaunchedEventName, shouldFlush: true)
default:
break
}
}
}
#endif