Skip to content

Commit e60ef8c

Browse files
feat: GAnalyticsV2 (#85)
* feat: GAnalyticsV2 * chore: including preference store in protocol * chore: working on implementation, removing old code * chore: adding AnalyticsServiceV2 removing grant and deny from public interface * test: repurposing test class * chore: adjusting instance method name to better distinguish * feat: make preference store property public to avoid having to initialise new one * chore: allow GAnalyticsV2 to be initialised with a preference store publicly * test: reintroducing preference store to protocol * ci: moving to mac catalyst on the CI * chore: changes based on comments * chore: removing whitespace
1 parent 7216b13 commit e60ef8c

10 files changed

Lines changed: 437 additions & 5 deletions

File tree

Sources/GAnalytics/GAnalytics.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Logging
77
///
88
/// An abstraction class for bringing Google Analytics (Firebase and Crashlytics) into the app from the Firebase package.
99
/// To provide user-specific insights for logging app metrics and performance.
10+
@available(*, deprecated, renamed: "GAnalyticsV2", message: "Please consider moving to GAnalyticsV2, this type will be replaced shortly")
1011
public struct GAnalytics {
1112
private let app: AnalyticsApp.Type
1213
private let analytics: AnalyticsLogger.Type
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import Firebase
2+
import FirebaseAnalytics
3+
import FirebaseCrashlytics
4+
import Logging
5+
6+
/// GAnalyticsV2
7+
///
8+
/// An abstraction class for bringing Google Analytics (Firebase and Crashlytics) into the app from the Firebase package.
9+
/// To provide user-specific insights for logging app metrics and performance.
10+
public struct GAnalyticsV2 {
11+
static var analyticsApp: AnalyticsApp.Type = FirebaseApp.self
12+
public let analyticsPreferenceStore: AnalyticsPreferenceStore
13+
private let analyticsLogger: AnalyticsLogger.Type
14+
private let crashLogger: CrashLogger
15+
16+
/// Additional parameters for the application
17+
public var additionalParameters = [String: Any]()
18+
19+
init(analyticsPreferenceStore: AnalyticsPreferenceStore,
20+
analyticsLogger: AnalyticsLogger.Type,
21+
crashLogger: CrashLogger) {
22+
self.analyticsPreferenceStore = analyticsPreferenceStore
23+
self.analyticsLogger = analyticsLogger
24+
self.crashLogger = crashLogger
25+
}
26+
27+
public init(analyticsPreferenceStore: AnalyticsPreferenceStore) {
28+
self.init(analyticsPreferenceStore: analyticsPreferenceStore,
29+
analyticsLogger: Analytics.self,
30+
crashLogger: Crashlytics.crashlytics())
31+
}
32+
33+
/// Initialises the Firebase instance when launching the app.
34+
public static func configure() {
35+
analyticsApp.configure()
36+
}
37+
38+
/// Activates subscription to preference store events and updates based on existing preference.
39+
public func activate() {
40+
subscribeToPreferenceStore()
41+
updateAnalyticsPreference(analyticsPreferenceStore.hasAcceptedAnalytics)
42+
}
43+
44+
private func subscribeToPreferenceStore() {
45+
Task {
46+
for await value in analyticsPreferenceStore.stream() {
47+
updateAnalyticsPreference(value)
48+
}
49+
}
50+
}
51+
52+
private func updateAnalyticsPreference(_ preference: Bool?) {
53+
switch preference {
54+
case true:
55+
grantAnalyticsPermission()
56+
default:
57+
denyAnalyticsPermission()
58+
}
59+
}
60+
61+
/// Merging `parameters` dictionary parameter with `additionalParameters` property
62+
private func mergeAdditionalParameters(_ parameters: [String: Any]) -> [String: Any] {
63+
additionalParameters.merging(parameters) { $1 }
64+
}
65+
}
66+
67+
extension GAnalyticsV2: AnalyticsServiceV2 {
68+
public func addingAdditionalParameters(
69+
_ additionalParameters: [String: Any]
70+
) -> Self {
71+
var newCopy = self
72+
newCopy.additionalParameters = self.additionalParameters
73+
.merging(additionalParameters) { lhs, _ in
74+
lhs
75+
}
76+
return newCopy
77+
}
78+
79+
/// Tracks screens adding screen tracking parameters in Firebase package.
80+
public func trackScreen(_ screen: LoggableScreen,
81+
parameters params: [String: Any] = [:]) {
82+
var parameters = mergeAdditionalParameters(params)
83+
84+
parameters[AnalyticsParameterScreenName] = screen.name
85+
parameters[AnalyticsParameterScreenClass] = screen.name
86+
87+
analyticsLogger.logEvent(AnalyticsEventScreenView,
88+
parameters: parameters)
89+
}
90+
91+
public func trackScreen(_ screen: any LoggableScreenV2,
92+
parameters params: [String: Any]) {
93+
var parameters = mergeAdditionalParameters(params)
94+
95+
parameters[AnalyticsParameterScreenName] = screen.name
96+
parameters[AnalyticsParameterScreenClass] = screen.type.description
97+
98+
analyticsLogger.logEvent(AnalyticsEventScreenView,
99+
parameters: parameters)
100+
}
101+
102+
/// Logs events accepting the event name and parameters in Firebase package.
103+
public func logEvent(_ event: LoggableEvent, parameters params: [String: Any]) {
104+
let parameters = mergeAdditionalParameters(params)
105+
analyticsLogger.logEvent(event.name, parameters: parameters)
106+
}
107+
108+
/// Logs crashes accepting an error in Firebase package.
109+
public func logCrash(_ error: NSError) {
110+
crashLogger.record(error: error)
111+
}
112+
113+
/// Granting analytics and crashlytics permissions in Firebase package.
114+
func grantAnalyticsPermission() {
115+
analyticsLogger.setAnalyticsCollectionEnabled(true)
116+
crashLogger.setCrashlyticsCollectionEnabled(true)
117+
}
118+
119+
/// Denying analytics and crashlytics permissions in Firebase package.
120+
func denyAnalyticsPermission() {
121+
analyticsLogger.setAnalyticsCollectionEnabled(false)
122+
analyticsLogger.resetAnalyticsData()
123+
124+
crashLogger.setCrashlyticsCollectionEnabled(false)
125+
}
126+
}

Sources/Logging/AnalyticsService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Foundation
33
/// AnalyticsService
44
///
55
/// A protocol for Types to log analytics to a third-party analytics service.
6+
@available(*, deprecated, renamed: "AnalyticsServiceV2", message: "Please consider moving to AnalyticsServiceV2, this protocol will be replaced shortly")
67
public protocol AnalyticsService: LoggingService {
78
var additionalParameters: [String: Any] { get set }
89
func addingAdditionalParameters(_ additionalParameters: [String: Any]) -> Self
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Foundation
2+
3+
/// AnalyticsServiceV2
4+
///
5+
/// A protocol for Types to log analytics to a third-party analytics service.
6+
public protocol AnalyticsServiceV2: LoggingService {
7+
var analyticsPreferenceStore: AnalyticsPreferenceStore { get }
8+
9+
var additionalParameters: [String: Any] { get set }
10+
func addingAdditionalParameters(_ additionalParameters: [String: Any]) -> Self
11+
12+
@available(*, deprecated, renamed: "trackScreen", message: "Please use LoggableScreenV2")
13+
func trackScreen(_ screen: LoggableScreen)
14+
15+
@available(*, deprecated, renamed: "trackScreen", message: "Please use LoggableScreenV2")
16+
func trackScreen(_ screen: LoggableScreen, parameters: [String: Any])
17+
18+
func trackScreen(_ screen: any LoggableScreenV2, parameters: [String: Any])
19+
20+
func logCrash(_ crash: NSError)
21+
func logCrash(_ crash: Error)
22+
}
23+
24+
extension AnalyticsServiceV2 {
25+
/// Protocol method for screen tracking, calling the conforming type's method for adding screen tracking parameters.
26+
public func trackScreen(_ screen: LoggableScreen) {
27+
trackScreen(screen, parameters: [:])
28+
}
29+
30+
/// Protocol method for crash logging, calling the conforming type's method for passing errors as `NSError`s.
31+
public func logCrash(_ crash: Error) {
32+
logCrash(crash as NSError)
33+
}
34+
}

Sources/Logging/Privacy/AnalyticsPreferenceStore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public protocol AnalyticsPreferenceStore {
77

88
public final class UserDefaultsPreferenceStore: AnalyticsPreferenceStore {
99
private let defaults: UserDefaults
10-
private var subscribers: [AsyncStream<Bool>.Continuation] = []
10+
private var subscribers = [AsyncStream<Bool>.Continuation]()
1111

1212
init(defaults: UserDefaults) {
1313
self.defaults = defaults
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extension Task where Success == Never, Failure == Never {
2+
static func sleep(seconds: Double) async throws {
3+
try await sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
4+
}
5+
}

Tests/GAnalyticsTests/GAnalyticsTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ extension GAnalyticsTests {
122122
let continuation = try XCTUnwrap(preferenceStore.subscribers.first)
123123
continuation.yield(true)
124124

125-
try await Task.sleep(nanoseconds: 100_000)
125+
try await Task.sleep(seconds: 1)
126126

127127
XCTAssertEqual(analyticsLogger.isAnalyticsCollectionEnabled, true)
128128
XCTAssertEqual(crashLogger.isCollectionEnabled, true)
@@ -138,7 +138,7 @@ extension GAnalyticsTests {
138138
let continuation = try XCTUnwrap(preferenceStore.subscribers.first)
139139
continuation.yield(false)
140140

141-
try await Task.sleep(nanoseconds: 100_000)
141+
try await Task.sleep(seconds: 1)
142142

143143
XCTAssertTrue(analyticsLogger.didResetAnalyticsData)
144144
XCTAssertEqual(analyticsLogger.isAnalyticsCollectionEnabled, false)

0 commit comments

Comments
 (0)